wrangler 0.0.0-e3cab74 → 0.0.0-e3ce82b

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.
Files changed (139) hide show
  1. package/Cloudflare_CA.pem +18 -0
  2. package/README.md +27 -14
  3. package/bin/wrangler.js +49 -5
  4. package/miniflare-dist/index.mjs +4886 -0
  5. package/package.json +43 -28
  6. package/pages/functions/buildPlugin.ts +105 -0
  7. package/pages/functions/buildWorker.ts +93 -4
  8. package/pages/functions/filepath-routing.test.ts +136 -25
  9. package/pages/functions/filepath-routing.ts +14 -3
  10. package/pages/functions/routes.ts +6 -1
  11. package/pages/functions/template-plugin.ts +147 -0
  12. package/pages/functions/template-worker.ts +21 -8
  13. package/src/__tests__/config-cache.test.ts +36 -0
  14. package/src/__tests__/configuration.test.ts +1713 -411
  15. package/src/__tests__/dev.test.tsx +954 -84
  16. package/src/__tests__/guess-worker-format.test.ts +37 -18
  17. package/src/__tests__/helpers/mock-account-id.ts +22 -4
  18. package/src/__tests__/helpers/mock-cfetch.ts +8 -4
  19. package/src/__tests__/helpers/mock-console.ts +35 -10
  20. package/src/__tests__/helpers/mock-dialogs.ts +67 -2
  21. package/src/__tests__/helpers/mock-http-server.ts +46 -0
  22. package/src/__tests__/helpers/mock-istty.ts +27 -0
  23. package/src/__tests__/helpers/mock-kv.ts +9 -16
  24. package/src/__tests__/helpers/mock-oauth-flow.ts +255 -0
  25. package/src/__tests__/helpers/mock-stdin.ts +103 -0
  26. package/src/__tests__/helpers/run-in-tmp.ts +10 -0
  27. package/src/__tests__/helpers/write-worker-source.ts +31 -0
  28. package/src/__tests__/https-options.test.ts +130 -0
  29. package/src/__tests__/index.test.ts +138 -747
  30. package/src/__tests__/init.test.ts +1637 -0
  31. package/src/__tests__/jest.setup.ts +28 -8
  32. package/src/__tests__/kv.test.ts +598 -258
  33. package/src/__tests__/logger.test.ts +115 -0
  34. package/src/__tests__/package-manager.test.ts +154 -7
  35. package/src/__tests__/pages.test.ts +754 -0
  36. package/src/__tests__/parse.test.ts +8 -0
  37. package/src/__tests__/publish.test.ts +4374 -1025
  38. package/src/__tests__/r2.test.ts +69 -34
  39. package/src/__tests__/route.test.ts +45 -0
  40. package/src/__tests__/secret.test.ts +228 -73
  41. package/src/__tests__/tail.test.ts +146 -20
  42. package/src/__tests__/user.test.ts +131 -0
  43. package/src/__tests__/whoami.test.tsx +60 -7
  44. package/src/abort.d.ts +3 -0
  45. package/src/bundle.ts +75 -11
  46. package/src/cfetch/index.ts +21 -4
  47. package/src/cfetch/internal.ts +29 -20
  48. package/src/cli.ts +5 -11
  49. package/src/config/README.md +7 -7
  50. package/src/config/config.ts +69 -100
  51. package/src/config/environment.ts +119 -4
  52. package/src/config/index.ts +177 -4
  53. package/src/config/validation-helpers.ts +237 -23
  54. package/src/config/validation.ts +722 -191
  55. package/src/config-cache.ts +45 -0
  56. package/src/{api/preview.ts → create-worker-preview.ts} +65 -27
  57. package/src/{api/form_data.ts → create-worker-upload-form.ts} +55 -18
  58. package/src/dev/dev-vars.ts +36 -0
  59. package/src/dev/dev.tsx +401 -0
  60. package/src/dev/local.tsx +322 -0
  61. package/src/dev/remote.tsx +237 -0
  62. package/src/dev/use-esbuild.ts +118 -0
  63. package/src/dialogs.tsx +64 -6
  64. package/src/durable.ts +102 -0
  65. package/src/entry.ts +279 -14
  66. package/src/environment-variables.ts +3 -1
  67. package/src/errors.ts +11 -0
  68. package/src/https-options.ts +123 -0
  69. package/src/index.tsx +1386 -867
  70. package/src/inspect.ts +64 -11
  71. package/src/intl-polyfill.d.ts +139 -0
  72. package/src/{kv.tsx → kv.ts} +195 -62
  73. package/src/logger.ts +66 -0
  74. package/src/miniflare-cli/README.md +30 -0
  75. package/src/miniflare-cli/enum-keys.ts +17 -0
  76. package/src/miniflare-cli/index.ts +41 -0
  77. package/src/miniflare-cli/request-context.ts +40 -0
  78. package/src/module-collection.ts +94 -6
  79. package/src/open-in-browser.ts +9 -3
  80. package/src/package-manager.ts +59 -11
  81. package/src/pages.tsx +1123 -75
  82. package/src/parse.ts +26 -6
  83. package/src/proxy.ts +159 -57
  84. package/src/publish.ts +581 -228
  85. package/src/selfsigned.d.ts +29 -0
  86. package/src/sites.tsx +130 -45
  87. package/src/tail/index.ts +2 -2
  88. package/src/tail/printing.ts +13 -8
  89. package/src/update-check.ts +19 -0
  90. package/src/user.tsx +275 -110
  91. package/src/whoami.tsx +5 -3
  92. package/src/{api/worker.ts → worker.ts} +24 -29
  93. package/src/zones.ts +73 -0
  94. package/templates/gitignore +171 -0
  95. package/templates/new-worker-scheduled.js +17 -0
  96. package/templates/new-worker-scheduled.ts +32 -0
  97. package/templates/new-worker.ts +16 -1
  98. package/templates/static-asset-facade.js +1 -5
  99. package/wrangler-dist/cli.js +104803 -118375
  100. package/src/__tests__/helpers/mock-user.ts +0 -27
  101. package/src/__tests__/logout.test.ts +0 -50
  102. package/src/__tests__/sentry.test.ts +0 -146
  103. package/src/dev.tsx +0 -961
  104. package/src/guess-worker-format.ts +0 -68
  105. package/src/reporting.ts +0 -115
  106. package/vendor/@cloudflare/kv-asset-handler/CHANGELOG.md +0 -332
  107. package/vendor/@cloudflare/kv-asset-handler/LICENSE_APACHE +0 -176
  108. package/vendor/@cloudflare/kv-asset-handler/LICENSE_MIT +0 -25
  109. package/vendor/@cloudflare/kv-asset-handler/README.md +0 -245
  110. package/vendor/@cloudflare/kv-asset-handler/dist/index.d.ts +0 -32
  111. package/vendor/@cloudflare/kv-asset-handler/dist/index.js +0 -354
  112. package/vendor/@cloudflare/kv-asset-handler/dist/mocks.d.ts +0 -13
  113. package/vendor/@cloudflare/kv-asset-handler/dist/mocks.js +0 -148
  114. package/vendor/@cloudflare/kv-asset-handler/dist/test/getAssetFromKV.d.ts +0 -1
  115. package/vendor/@cloudflare/kv-asset-handler/dist/test/getAssetFromKV.js +0 -436
  116. package/vendor/@cloudflare/kv-asset-handler/dist/test/mapRequestToAsset.d.ts +0 -1
  117. package/vendor/@cloudflare/kv-asset-handler/dist/test/mapRequestToAsset.js +0 -40
  118. package/vendor/@cloudflare/kv-asset-handler/dist/test/serveSinglePageApp.d.ts +0 -1
  119. package/vendor/@cloudflare/kv-asset-handler/dist/test/serveSinglePageApp.js +0 -42
  120. package/vendor/@cloudflare/kv-asset-handler/dist/types.d.ts +0 -26
  121. package/vendor/@cloudflare/kv-asset-handler/dist/types.js +0 -31
  122. package/vendor/@cloudflare/kv-asset-handler/package.json +0 -52
  123. package/vendor/@cloudflare/kv-asset-handler/src/index.ts +0 -296
  124. package/vendor/@cloudflare/kv-asset-handler/src/mocks.ts +0 -136
  125. package/vendor/@cloudflare/kv-asset-handler/src/test/getAssetFromKV.ts +0 -464
  126. package/vendor/@cloudflare/kv-asset-handler/src/test/mapRequestToAsset.ts +0 -33
  127. package/vendor/@cloudflare/kv-asset-handler/src/test/serveSinglePageApp.ts +0 -42
  128. package/vendor/@cloudflare/kv-asset-handler/src/types.ts +0 -39
  129. package/vendor/wrangler-mime/CHANGELOG.md +0 -289
  130. package/vendor/wrangler-mime/LICENSE +0 -21
  131. package/vendor/wrangler-mime/Mime.js +0 -97
  132. package/vendor/wrangler-mime/README.md +0 -187
  133. package/vendor/wrangler-mime/cli.js +0 -46
  134. package/vendor/wrangler-mime/index.js +0 -4
  135. package/vendor/wrangler-mime/lite.js +0 -4
  136. package/vendor/wrangler-mime/package.json +0 -52
  137. package/vendor/wrangler-mime/types/other.js +0 -1
  138. package/vendor/wrangler-mime/types/standard.js +0 -1
  139. package/wrangler-dist/cli.js.map +0 -7
@@ -0,0 +1,29 @@
1
+ declare module "selfsigned" {
2
+ import type { pki } from "node-forge";
3
+
4
+ export type Attributes = pki.CertificateField[];
5
+
6
+ export interface Options {
7
+ days?: number;
8
+ keySize?: number;
9
+ extensions?: unknown[];
10
+ algorithm?: string;
11
+ pkcs7?: boolean;
12
+ clientCertificate?: boolean;
13
+ clientCertificateCN?: string;
14
+ }
15
+
16
+ export interface Certificate {
17
+ private: string;
18
+ public: string;
19
+ cert: string;
20
+ fingerprint: string;
21
+ }
22
+
23
+ export function generate(attrs?: Attributes, options?: Options): Certificate;
24
+ export function generate(
25
+ attrs: Attributes,
26
+ options: Options,
27
+ callback: (err: Error, certificate: Certificate) => void
28
+ ): void;
29
+ }
package/src/sites.tsx CHANGED
@@ -1,34 +1,35 @@
1
+ import assert from "node:assert";
1
2
  import { readdir, readFile, stat } from "node:fs/promises";
2
3
  import * as path from "node:path";
3
4
  import ignore from "ignore";
4
5
  import xxhash from "xxhash-wasm";
5
6
  import {
6
- createNamespace,
7
- listNamespaceKeys,
8
- listNamespaces,
9
- putBulkKeyValue,
7
+ createKVNamespace,
8
+ listKVNamespaceKeys,
9
+ listKVNamespaces,
10
+ putKVBulkKeyValue,
11
+ deleteKVBulkKeyValue,
10
12
  } from "./kv";
13
+ import { logger } from "./logger";
11
14
  import type { Config } from "./config";
15
+ import type { KeyValue } from "./kv";
12
16
  import type { XXHashAPI } from "xxhash-wasm";
13
17
 
14
18
  /** Paths to always ignore. */
15
- const ALWAYS_IGNORE = ["node_modules"];
16
- const HIDDEN_FILES_TO_INCLUDE = [
19
+ const ALWAYS_IGNORE = new Set(["node_modules"]);
20
+ const HIDDEN_FILES_TO_INCLUDE = new Set([
17
21
  ".well-known", // See https://datatracker.ietf.org/doc/html/rfc8615
18
- ];
22
+ ]);
19
23
 
20
24
  async function* getFilesInFolder(dirPath: string): AsyncIterable<string> {
21
25
  const files = await readdir(dirPath, { withFileTypes: true });
22
26
  for (const file of files) {
23
27
  // Skip files that we never want to process.
24
- if (ALWAYS_IGNORE.some((p) => file.name === p)) {
28
+ if (ALWAYS_IGNORE.has(file.name)) {
25
29
  continue;
26
30
  }
27
31
  // Skip hidden files (starting with .) except for some special ones
28
- if (
29
- file.name.startsWith(".") &&
30
- !HIDDEN_FILES_TO_INCLUDE.some((p) => file.name === p)
31
- ) {
32
+ if (file.name.startsWith(".") && !HIDDEN_FILES_TO_INCLUDE.has(file.name)) {
32
33
  continue;
33
34
  }
34
35
  // TODO: follow symlinks??
@@ -75,15 +76,15 @@ async function createKVNamespaceIfNotAlreadyExisting(
75
76
  ) {
76
77
  // check if it already exists
77
78
  // TODO: this is super inefficient, should be made better
78
- const namespaces = await listNamespaces(accountId);
79
+ const namespaces = await listKVNamespaces(accountId);
79
80
  const found = namespaces.find((x) => x.title === title);
80
81
  if (found) {
81
82
  return { created: false, id: found.id };
82
83
  }
83
84
 
84
85
  // else we make the namespace
85
- const id = await createNamespace(accountId, title);
86
- console.log(`🌀 Created namespace for Workers Site "${title}"`);
86
+ const id = await createKVNamespace(accountId, title);
87
+ logger.log(`🌀 Created namespace for Workers Site "${title}"`);
87
88
 
88
89
  return {
89
90
  created: true,
@@ -103,10 +104,11 @@ async function createKVNamespaceIfNotAlreadyExisting(
103
104
  * asset in the KV namespace.
104
105
  */
105
106
  export async function syncAssets(
106
- accountId: string,
107
+ accountId: string | undefined,
107
108
  scriptName: string,
108
109
  siteAssets: AssetPaths | undefined,
109
- preview: boolean
110
+ preview: boolean,
111
+ dryRun: boolean | undefined
110
112
  ): Promise<{
111
113
  manifest: { [filePath: string]: string } | undefined;
112
114
  namespace: string | undefined;
@@ -115,59 +117,113 @@ export async function syncAssets(
115
117
  return { manifest: undefined, namespace: undefined };
116
118
  }
117
119
 
120
+ if (dryRun) {
121
+ logger.log("(Note: doing a dry run, not uploading or deleting anything.)");
122
+ return { manifest: undefined, namespace: undefined };
123
+ }
124
+ assert(accountId, "Missing accountId");
125
+
118
126
  const title = `__${scriptName}-workers_sites_assets${
119
127
  preview ? "_preview" : ""
120
128
  }`;
129
+
121
130
  const { id: namespace } = await createKVNamespaceIfNotAlreadyExisting(
122
131
  title,
123
132
  accountId
124
133
  );
125
134
 
126
135
  // let's get all the keys in this namespace
127
- const result = await listNamespaceKeys(accountId, namespace);
128
- const keys = new Set(result.map((x) => x.name));
136
+ const namespaceKeysResponse = await listKVNamespaceKeys(accountId, namespace);
137
+ const namespaceKeys = new Set(namespaceKeysResponse.map((x) => x.name));
129
138
 
130
139
  const manifest: Record<string, string> = {};
131
- const upload: {
132
- key: string;
133
- value: string;
134
- base64: boolean;
135
- }[] = [];
140
+
141
+ // A batch of uploads where each bucket has to be less than 100mb
142
+ const uploadBuckets: KeyValue[][] = [];
143
+ // The "live" bucket that we'll keep filling until it's just below 100mb
144
+ let uploadBucket: KeyValue[] = [];
145
+ // A size counter for the live bucket
146
+ let uploadBucketSize = 0;
136
147
 
137
148
  const include = createPatternMatcher(siteAssets.includePatterns, false);
138
149
  const exclude = createPatternMatcher(siteAssets.excludePatterns, true);
139
150
  const hasher = await xxhash();
140
151
 
141
- // TODO: this can be more efficient by parallelising
142
- for await (const file of getFilesInFolder(siteAssets.baseDirectory)) {
143
- if (!include(file)) {
152
+ const assetDirectory = path.join(
153
+ siteAssets.baseDirectory,
154
+ siteAssets.assetDirectory
155
+ );
156
+ for await (const absAssetFile of getFilesInFolder(assetDirectory)) {
157
+ const assetFile = path.relative(assetDirectory, absAssetFile);
158
+ if (!include(assetFile)) {
144
159
  continue;
145
160
  }
146
- if (exclude(file)) {
161
+ if (exclude(assetFile)) {
147
162
  continue;
148
163
  }
149
164
 
150
- await validateAssetSize(file);
151
- console.log(`reading ${file}...`);
152
- const content = await readFile(file, "base64");
153
-
154
- const assetKey = await hashAsset(hasher, file, content);
165
+ logger.log(`Reading ${assetFile}...`);
166
+ const content = await readFile(absAssetFile, "base64");
167
+ await validateAssetSize(absAssetFile, assetFile);
168
+ // while KV accepts files that are 25 MiB **before** b64 encoding
169
+ // the overall bucket size must be below 100 MiB **after** b64 encoding
170
+ const assetSize = Buffer.from(content).length;
171
+ const assetKey = hashAsset(hasher, assetFile, content);
155
172
  validateAssetKey(assetKey);
156
173
 
157
174
  // now put each of the files into kv
158
- if (!keys.has(assetKey)) {
159
- console.log(`uploading as ${assetKey}...`);
160
- upload.push({
175
+ if (!namespaceKeys.has(assetKey)) {
176
+ logger.log(`Uploading as ${assetKey}...`);
177
+
178
+ // Check if adding this asset to the bucket would
179
+ // push it over the 100 MiB limit KV bulk API limit
180
+ if (uploadBucketSize + assetSize > 100 * 1024 * 1024) {
181
+ // If so, move the current bucket into the batch,
182
+ // and reset the counter/bucket
183
+ uploadBuckets.push(uploadBucket);
184
+ uploadBucketSize = 0;
185
+ uploadBucket = [];
186
+ }
187
+
188
+ // Update the bucket and the size counter
189
+ uploadBucketSize += assetSize;
190
+ uploadBucket.push({
161
191
  key: assetKey,
162
192
  value: content,
163
193
  base64: true,
164
194
  });
165
195
  } else {
166
- console.log(`skipping - already uploaded`);
196
+ logger.log(`Skipping - already uploaded.`);
167
197
  }
168
- manifest[path.relative(siteAssets.baseDirectory, file)] = assetKey;
198
+
199
+ // Remove the key from the set so we know what we've already uploaded
200
+ namespaceKeys.delete(assetKey);
201
+
202
+ // Prevent different manifest keys on windows
203
+ const manifestKey = urlSafe(path.relative(assetDirectory, absAssetFile));
204
+ manifest[manifestKey] = assetKey;
169
205
  }
170
- await putBulkKeyValue(accountId, namespace, JSON.stringify(upload));
206
+
207
+ // Add the last (potentially only) bucket to the batch
208
+ uploadBuckets.push(uploadBucket);
209
+
210
+ // keys now contains all the files we're deleting
211
+ for (const key of namespaceKeys) {
212
+ logger.log(`Deleting ${key} from the asset store...`);
213
+ }
214
+
215
+ // upload each bucket in parallel
216
+ const bucketsToPut = [];
217
+ for (const bucket of uploadBuckets) {
218
+ bucketsToPut.push(putKVBulkKeyValue(accountId, namespace, bucket));
219
+ }
220
+ await Promise.all(bucketsToPut);
221
+
222
+ // then delete all the assets that aren't used anymore
223
+ await deleteKVBulkKeyValue(accountId, namespace, Array.from(namespaceKeys));
224
+
225
+ logger.log("↗️ Done syncing assets");
226
+
171
227
  return { manifest, namespace };
172
228
  }
173
229
 
@@ -183,11 +239,20 @@ function createPatternMatcher(
183
239
  }
184
240
  }
185
241
 
186
- async function validateAssetSize(filePath: string) {
187
- const { size } = await stat(filePath);
242
+ /**
243
+ * validate that the passed-in file is below 25 MiB
244
+ * **PRIOR** to base64 encoding. 25 MiB is a KV limit
245
+ * @param absFilePath
246
+ * @param relativeFilePath
247
+ */
248
+ async function validateAssetSize(
249
+ absFilePath: string,
250
+ relativeFilePath: string
251
+ ): Promise<void> {
252
+ const { size } = await stat(absFilePath);
188
253
  if (size > 25 * 1024 * 1024) {
189
254
  throw new Error(
190
- `File ${filePath} is too big, it should be under 25 MiB. See https://developers.cloudflare.com/workers/platform/limits#kv-limits`
255
+ `File ${relativeFilePath} is too big, it should be under 25 MiB. See https://developers.cloudflare.com/workers/platform/limits#kv-limits`
191
256
  );
192
257
  }
193
258
  }
@@ -213,8 +278,23 @@ function urlSafe(filePath: string): string {
213
278
  * Information about the assets that should be uploaded
214
279
  */
215
280
  export interface AssetPaths {
281
+ /**
282
+ * Absolute path to the root of the project.
283
+ *
284
+ * This is the directory containing wrangler.toml or cwd if no config.
285
+ */
216
286
  baseDirectory: string;
287
+ /**
288
+ * The path to the assets directory, relative to the `baseDirectory`.
289
+ */
290
+ assetDirectory: string;
291
+ /**
292
+ * An array of patterns that match files that should be uploaded.
293
+ */
217
294
  includePatterns: string[];
295
+ /**
296
+ * An array of patterns that match files that should not be uploaded.
297
+ */
218
298
  excludePatterns: string[];
219
299
  }
220
300
 
@@ -227,13 +307,18 @@ export interface AssetPaths {
227
307
  */
228
308
  export function getAssetPaths(
229
309
  config: Config,
230
- baseDirectory = config.site?.bucket,
310
+ assetDirectory = config.site?.bucket,
231
311
  includePatterns = config.site?.include ?? [],
232
312
  excludePatterns = config.site?.exclude ?? []
233
- ): undefined | AssetPaths {
234
- return baseDirectory
313
+ ): AssetPaths | undefined {
314
+ const baseDirectory = path.resolve(
315
+ path.dirname(config.configPath ?? "wrangler.toml")
316
+ );
317
+
318
+ return assetDirectory
235
319
  ? {
236
320
  baseDirectory,
321
+ assetDirectory,
237
322
  includePatterns,
238
323
  excludePatterns,
239
324
  }
package/src/tail/index.ts CHANGED
@@ -161,7 +161,7 @@ export type TailEventMessage = {
161
161
  * Any logs sent out by the worker
162
162
  */
163
163
  logs: {
164
- message: unknown;
164
+ message: unknown[];
165
165
  level: string; // TODO: make this a union of possible values
166
166
  timestamp: number;
167
167
  }[];
@@ -179,7 +179,7 @@ export type TailEventMessage = {
179
179
  * Until workers-types exposes individual types for export, we'll have
180
180
  * to just re-define these types ourselves.
181
181
  */
182
- event: RequestEvent | ScheduledEvent;
182
+ event: RequestEvent | ScheduledEvent | undefined | null;
183
183
  };
184
184
 
185
185
  /**
@@ -1,3 +1,4 @@
1
+ import { logger } from "../logger";
1
2
  import type { RequestEvent, ScheduledEvent, TailEventMessage } from ".";
2
3
  import type { Outcome } from "./filters";
3
4
  import type WebSocket from "ws";
@@ -12,25 +13,29 @@ export function prettyPrintLogs(data: WebSocket.RawData): void {
12
13
  ).toLocaleString();
13
14
  const outcome = prettifyOutcome(eventMessage.outcome);
14
15
 
15
- console.log(`"${cronPattern}" @ ${datetime} - ${outcome}`);
16
+ logger.log(`"${cronPattern}" @ ${datetime} - ${outcome}`);
16
17
  } else {
17
- const requestMethod = eventMessage.event.request.method.toUpperCase();
18
- const url = eventMessage.event.request.url;
18
+ const requestMethod = eventMessage.event?.request.method.toUpperCase();
19
+ const url = eventMessage.event?.request.url;
19
20
  const outcome = prettifyOutcome(eventMessage.outcome);
20
21
  const datetime = new Date(eventMessage.eventTimestamp).toLocaleString();
21
22
 
22
- console.log(`${requestMethod} ${url} - ${outcome} @ ${datetime}`);
23
+ logger.log(
24
+ url
25
+ ? `${requestMethod} ${url} - ${outcome} @ ${datetime}`
26
+ : `[missing request] - ${outcome} @ ${datetime}`
27
+ );
23
28
  }
24
29
 
25
30
  if (eventMessage.logs.length > 0) {
26
31
  eventMessage.logs.forEach(({ level, message }) => {
27
- console.log(` (${level}) ${message}`);
32
+ logger.log(` (${level})`, ...message);
28
33
  });
29
34
  }
30
35
 
31
36
  if (eventMessage.exceptions.length > 0) {
32
37
  eventMessage.exceptions.forEach(({ name, message }) => {
33
- console.error(` ${name}: ${message}`);
38
+ logger.error(` ${name}:`, message);
34
39
  });
35
40
  }
36
41
  }
@@ -40,9 +45,9 @@ export function jsonPrintLogs(data: WebSocket.RawData): void {
40
45
  }
41
46
 
42
47
  function isScheduledEvent(
43
- event: RequestEvent | ScheduledEvent
48
+ event: RequestEvent | ScheduledEvent | undefined | null
44
49
  ): event is ScheduledEvent {
45
- return "cron" in event;
50
+ return Boolean(event && "cron" in event);
46
51
  }
47
52
 
48
53
  function prettifyOutcome(outcome: Outcome): string {
@@ -0,0 +1,19 @@
1
+ import chalk from "chalk";
2
+ import checkForUpdate from "update-check";
3
+ import pkg from "../package.json";
4
+
5
+ export async function updateCheck(): Promise<string> {
6
+ let update = null;
7
+ try {
8
+ // default cache for update check is 1 day
9
+ update = await checkForUpdate(pkg, {
10
+ distTag: pkg.version.startsWith("0.0.0") ? "beta" : "latest",
11
+ });
12
+ } catch (err) {
13
+ // ignore error
14
+ }
15
+
16
+ if (update) return `(update available ${chalk.green(update.latest)})`;
17
+
18
+ return "";
19
+ }