wrangler 2.0.23 → 2.0.24

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 (55) hide show
  1. package/README.md +20 -2
  2. package/bin/wrangler.js +1 -1
  3. package/miniflare-dist/index.mjs +96 -34
  4. package/package.json +9 -4
  5. package/src/__tests__/configuration.test.ts +88 -16
  6. package/src/__tests__/dev.test.tsx +3 -0
  7. package/src/__tests__/generate.test.ts +93 -0
  8. package/src/__tests__/helpers/mock-cfetch.ts +54 -2
  9. package/src/__tests__/index.test.ts +10 -27
  10. package/src/__tests__/jest.setup.ts +31 -1
  11. package/src/__tests__/kv.test.ts +2 -2
  12. package/src/__tests__/metrics.test.ts +5 -0
  13. package/src/__tests__/publish.test.ts +497 -254
  14. package/src/__tests__/r2.test.ts +155 -71
  15. package/src/__tests__/user.test.ts +1 -0
  16. package/src/__tests__/validate-dev-props.test.ts +56 -0
  17. package/src/__tests__/whoami.test.tsx +60 -1
  18. package/src/bundle.ts +278 -44
  19. package/src/cfetch/internal.ts +34 -2
  20. package/src/config/config.ts +7 -2
  21. package/src/config/environment.ts +40 -8
  22. package/src/config/index.ts +13 -0
  23. package/src/config/validation.ts +101 -7
  24. package/src/create-worker-upload-form.ts +25 -0
  25. package/src/dev/dev.tsx +107 -28
  26. package/src/dev/local.tsx +20 -10
  27. package/src/dev/remote.tsx +39 -8
  28. package/src/dev/use-esbuild.ts +25 -0
  29. package/src/dev/validate-dev-props.ts +31 -0
  30. package/src/dev-registry.tsx +157 -0
  31. package/src/dev.tsx +93 -75
  32. package/src/generate.ts +112 -14
  33. package/src/index.tsx +182 -4
  34. package/src/inspect.ts +93 -5
  35. package/src/metrics/index.ts +1 -0
  36. package/src/metrics/metrics-dispatcher.ts +1 -0
  37. package/src/metrics/metrics-usage-headers.ts +24 -0
  38. package/src/metrics/send-event.ts +2 -2
  39. package/src/module-collection.ts +3 -3
  40. package/src/pages/constants.ts +1 -0
  41. package/src/pages/deployments.tsx +1 -1
  42. package/src/pages/dev.tsx +6 -1
  43. package/src/pages/publish.tsx +1 -1
  44. package/src/pages/upload.tsx +32 -13
  45. package/src/publish.ts +126 -112
  46. package/src/r2.ts +68 -0
  47. package/src/user/user.tsx +20 -2
  48. package/src/whoami.tsx +79 -1
  49. package/src/worker.ts +12 -0
  50. package/templates/first-party-worker-module-facade.ts +18 -0
  51. package/templates/format-dev-errors.ts +32 -0
  52. package/templates/{static-asset-facade.js → serve-static-assets.ts} +21 -7
  53. package/templates/service-bindings-module-facade.js +51 -0
  54. package/templates/service-bindings-sw-facade.js +39 -0
  55. package/wrangler-dist/cli.js +40192 -15265
@@ -0,0 +1,31 @@
1
+ import type { DevProps } from "./dev";
2
+
3
+ export function validateDevProps(props: DevProps) {
4
+ if (
5
+ !props.isWorkersSite &&
6
+ props.assetPaths &&
7
+ props.entry.format === "service-worker"
8
+ ) {
9
+ throw new Error(
10
+ "You cannot use the service-worker format with an `assets` directory yet. For information on how to migrate to the module-worker format, see: https://developers.cloudflare.com/workers/learning/migrating-to-module-workers/"
11
+ );
12
+ }
13
+
14
+ if (props.bindings.wasm_modules && props.entry.format === "modules") {
15
+ throw new Error(
16
+ "You cannot configure [wasm_modules] with an ES module worker. Instead, import the .wasm module directly in your code"
17
+ );
18
+ }
19
+
20
+ if (props.bindings.text_blobs && props.entry.format === "modules") {
21
+ throw new Error(
22
+ "You cannot configure [text_blobs] with an ES module worker. Instead, import the file directly in your code, and optionally configure `[rules]` in your wrangler.toml"
23
+ );
24
+ }
25
+
26
+ if (props.bindings.data_blobs && props.entry.format === "modules") {
27
+ throw new Error(
28
+ "You cannot configure [data_blobs] with an ES module worker. Instead, import the file directly in your code, and optionally configure `[rules]` in your wrangler.toml"
29
+ );
30
+ }
31
+ }
@@ -0,0 +1,157 @@
1
+ import http from "http";
2
+ import net from "net";
3
+ import bodyParser from "body-parser";
4
+ import express from "express";
5
+ import { createHttpTerminator } from "http-terminator";
6
+ import { fetch } from "undici";
7
+ import { logger } from "./logger";
8
+ import type { Server } from "http";
9
+ import type { HttpTerminator } from "http-terminator";
10
+
11
+ const DEV_REGISTRY_PORT = "6284";
12
+ const DEV_REGISTRY_HOST = `http://localhost:${DEV_REGISTRY_PORT}`;
13
+
14
+ let server: Server;
15
+ let terminator: HttpTerminator;
16
+
17
+ export type WorkerRegistry = Record<string, WorkerDefinition>;
18
+
19
+ type WorkerDefinition = {
20
+ port: number | undefined;
21
+ protocol: "http" | "https" | undefined;
22
+ host: string | undefined;
23
+ mode: "local" | "remote";
24
+ headers?: Record<string, string>;
25
+ };
26
+
27
+ /**
28
+ * A helper function to check whether our service registry is already running
29
+ */
30
+ async function isPortAvailable() {
31
+ return new Promise((resolve, reject) => {
32
+ const netServer = net
33
+ .createServer()
34
+ .once("error", (err) => {
35
+ netServer.close();
36
+ if ((err as unknown as { code: string }).code === "EADDRINUSE") {
37
+ resolve(false);
38
+ } else {
39
+ reject(err);
40
+ }
41
+ })
42
+ .once("listening", () => {
43
+ netServer.close();
44
+ resolve(true);
45
+ });
46
+ netServer.listen(DEV_REGISTRY_PORT);
47
+ });
48
+ }
49
+
50
+ const jsonBodyParser = bodyParser.json();
51
+
52
+ /**
53
+ * Start the service registry. It's a simple server
54
+ * that exposes endpoints for registering and unregistering
55
+ * services, as well as getting the state of the registry.
56
+ */
57
+ export async function startWorkerRegistry() {
58
+ if ((await isPortAvailable()) && !server) {
59
+ const app = express();
60
+
61
+ let workers: WorkerRegistry = {};
62
+ app
63
+ .get("/workers", async (req, res) => {
64
+ res.json(workers);
65
+ })
66
+ .post("/workers/:workerId", jsonBodyParser, async (req, res) => {
67
+ workers[req.params.workerId] = req.body;
68
+ res.json(null);
69
+ })
70
+ .delete(`/workers/:workerId`, async (req, res) => {
71
+ delete workers[req.params.workerId];
72
+ res.json(null);
73
+ })
74
+ .delete("/workers", async (req, res) => {
75
+ workers = {};
76
+ res.json(null);
77
+ });
78
+ server = http.createServer(app);
79
+ terminator = createHttpTerminator({ server });
80
+ server.listen(DEV_REGISTRY_PORT);
81
+ }
82
+ }
83
+
84
+ /**
85
+ * Stop the service registry.
86
+ */
87
+ export async function stopWorkerRegistry() {
88
+ await terminator?.terminate();
89
+ }
90
+
91
+ /**
92
+ * Register a worker in the registry.
93
+ */
94
+ export async function registerWorker(
95
+ name: string,
96
+ definition: WorkerDefinition
97
+ ) {
98
+ try {
99
+ return await fetch(`${DEV_REGISTRY_HOST}/workers/${name}`, {
100
+ method: "POST",
101
+ headers: {
102
+ "Content-Type": "application/json",
103
+ },
104
+ body: JSON.stringify(definition),
105
+ });
106
+ } catch (e) {
107
+ if (
108
+ !["ECONNRESET", "ECONNREFUSED"].includes(
109
+ (e as unknown as { cause?: { code?: string } }).cause?.code || "___"
110
+ )
111
+ ) {
112
+ logger.error("Failed to register worker in local service registry", e);
113
+ } else {
114
+ logger.debug("Failed to register worker in local service registry", e);
115
+ }
116
+ }
117
+ }
118
+
119
+ /**
120
+ * Unregister a worker from the registry.
121
+ */
122
+ export async function unregisterWorker(name: string) {
123
+ try {
124
+ await fetch(`${DEV_REGISTRY_HOST}/workers/${name}`, {
125
+ method: "DELETE",
126
+ });
127
+ } catch (e) {
128
+ if (
129
+ !["ECONNRESET", "ECONNREFUSED"].includes(
130
+ (e as unknown as { cause?: { code?: string } }).cause?.code || "___"
131
+ )
132
+ ) {
133
+ throw e;
134
+ // logger.error("failed to unregister worker", e);
135
+ }
136
+ }
137
+ }
138
+
139
+ /**
140
+ * Get the state of the service registry.
141
+ */
142
+ export async function getRegisteredWorkers(): Promise<
143
+ WorkerRegistry | undefined
144
+ > {
145
+ try {
146
+ const response = await fetch(`${DEV_REGISTRY_HOST}/workers`);
147
+ return (await response.json()) as WorkerRegistry;
148
+ } catch (e) {
149
+ if (
150
+ !["ECONNRESET", "ECONNREFUSED"].includes(
151
+ (e as unknown as { cause?: { code?: string } }).cause?.code || "___"
152
+ )
153
+ ) {
154
+ throw e;
155
+ }
156
+ }
157
+ }
package/src/dev.tsx CHANGED
@@ -260,9 +260,7 @@ export async function devHandler(args: ArgumentsCamelCase<DevArgs>) {
260
260
  }
261
261
  }
262
262
 
263
- type StartDevOptions = ArgumentsCamelCase<DevArgs> & {
264
- // These options can be passed in directly when called with the `wrangler.dev()` API.
265
- // They aren't exposed as CLI arguments.
263
+ export type AdditionalDevProps = {
266
264
  vars?: {
267
265
  [key: string]: unknown;
268
266
  };
@@ -277,9 +275,14 @@ type StartDevOptions = ArgumentsCamelCase<DevArgs> & {
277
275
  script_name?: string | undefined;
278
276
  environment?: string | undefined;
279
277
  }[];
280
- forceLocal?: boolean;
281
- enablePagesAssetsServiceBinding?: EnablePagesAssetsServiceBindingOptions;
282
278
  };
279
+ type StartDevOptions = ArgumentsCamelCase<DevArgs> &
280
+ // These options can be passed in directly when called with the `wrangler.dev()` API.
281
+ // They aren't exposed as CLI arguments.
282
+ AdditionalDevProps & {
283
+ forceLocal?: boolean;
284
+ enablePagesAssetsServiceBinding?: EnablePagesAssetsServiceBindingOptions;
285
+ };
283
286
 
284
287
  export async function startDev(args: StartDevOptions) {
285
288
  let watcher: ReturnType<typeof watch> | undefined;
@@ -296,11 +299,6 @@ export async function startDev(args: StartDevOptions) {
296
299
  ((args.script &&
297
300
  findWranglerToml(path.dirname(args.script))) as ConfigPath);
298
301
  let config = readConfig(configPath, args);
299
- await metrics.sendMetricsEvent(
300
- "run dev",
301
- { local: args.local },
302
- { sendMetrics: config.send_metrics, offline: args.local }
303
- );
304
302
 
305
303
  if (config.configPath) {
306
304
  watcher = watch(config.configPath, {
@@ -322,6 +320,15 @@ export async function startDev(args: StartDevOptions) {
322
320
  "dev"
323
321
  );
324
322
 
323
+ await metrics.sendMetricsEvent(
324
+ "run dev",
325
+ {
326
+ local: args.local,
327
+ usesTypeScript: /\.tsx?$/.test(entry.file),
328
+ },
329
+ { sendMetrics: config.send_metrics, offline: args.local }
330
+ );
331
+
325
332
  if (config.services && config.services.length > 0) {
326
333
  logger.warn(
327
334
  `This worker is bound to live services: ${config.services
@@ -412,77 +419,18 @@ export async function startDev(args: StartDevOptions) {
412
419
  );
413
420
  }
414
421
 
415
- // eslint-disable-next-line no-inner-declarations
416
- async function getBindings(
417
- configParam: Config
418
- ): Promise<CfWorkerInit["bindings"]> {
419
- return {
420
- kv_namespaces: [
421
- ...(configParam.kv_namespaces || []).map(
422
- ({ binding, preview_id, id: _id }) => {
423
- // In `dev`, we make folks use a separate kv namespace called
424
- // `preview_id` instead of `id` so that they don't
425
- // break production data. So here we check that a `preview_id`
426
- // has actually been configured.
427
- // This whole block of code will be obsoleted in the future
428
- // when we have copy-on-write for previews on edge workers.
429
- if (!preview_id) {
430
- // TODO: This error has to be a _lot_ better, ideally just asking
431
- // to create a preview namespace for the user automatically
432
- throw new Error(
433
- `In development, you should use a separate kv namespace than the one you'd use in production. Please create a new kv namespace with "wrangler kv:namespace create <name> --preview" and add its id as preview_id to the kv_namespace "${binding}" in your wrangler.toml`
434
- ); // Ugh, I really don't like this message very much
435
- }
436
- return {
437
- binding,
438
- id: preview_id,
439
- };
440
- }
441
- ),
442
- ...(args.kv || []),
443
- ],
444
- // Use a copy of combinedVars since we're modifying it later
445
- vars: {
446
- ...getVarsForDev(configParam),
447
- ...args.vars,
448
- },
449
- wasm_modules: configParam.wasm_modules,
450
- text_blobs: configParam.text_blobs,
451
- data_blobs: configParam.data_blobs,
452
- durable_objects: {
453
- bindings: [
454
- ...(configParam.durable_objects || { bindings: [] }).bindings,
455
- ...(args.durableObjects || []),
456
- ],
457
- },
458
- r2_buckets: configParam.r2_buckets?.map(
459
- ({ binding, preview_bucket_name, bucket_name: _bucket_name }) => {
460
- // same idea as kv namespace preview id,
461
- // same copy-on-write TODO
462
- if (!preview_bucket_name) {
463
- throw new Error(
464
- `In development, you should use a separate r2 bucket than the one you'd use in production. Please create a new r2 bucket with "wrangler r2 bucket create <name>" and add its name as preview_bucket_name to the r2_buckets "${binding}" in your wrangler.toml`
465
- );
466
- }
467
- return {
468
- binding,
469
- bucket_name: preview_bucket_name,
470
- };
471
- }
472
- ),
473
- worker_namespaces: configParam.worker_namespaces,
474
- services: configParam.services,
475
- unsafe: configParam.unsafe?.bindings,
476
- };
477
- }
478
-
479
422
  const getLocalPort = memoizeGetPort(DEFAULT_LOCAL_PORT);
480
423
  const getInspectorPort = memoizeGetPort(DEFAULT_INSPECTOR_PORT);
481
424
 
482
425
  // eslint-disable-next-line no-inner-declarations
483
426
  async function getDevReactElement(configParam: Config) {
484
427
  // now log all available bindings into the terminal
485
- const bindings = await getBindings(configParam);
428
+ const bindings = await getBindings(configParam, {
429
+ kv: args.kv,
430
+ vars: args.vars,
431
+ durableObjects: args.durableObjects,
432
+ });
433
+
486
434
  // mask anything that was overridden in .dev.vars
487
435
  // so that we don't log potential secrets into the terminal
488
436
  const maskedVars = { ...bindings.vars };
@@ -537,6 +485,7 @@ export async function startDev(args: StartDevOptions) {
537
485
  liveReload={args.liveReload || false}
538
486
  accountId={config.account_id || getAccountFromCache()?.id}
539
487
  assetPaths={assetPaths}
488
+ assetsConfig={config.assets}
540
489
  port={args.port || config.dev.port || (await getLocalPort())}
541
490
  ip={args.ip || config.dev.ip}
542
491
  inspectorPort={
@@ -562,6 +511,8 @@ export async function startDev(args: StartDevOptions) {
562
511
  showInteractiveDevSession={args.showInteractiveDevSession}
563
512
  forceLocal={args.forceLocal}
564
513
  enablePagesAssetsServiceBinding={args.enablePagesAssetsServiceBinding}
514
+ firstPartyWorker={config.first_party_worker}
515
+ sendMetrics={config.send_metrics}
565
516
  />
566
517
  );
567
518
  }
@@ -596,3 +547,70 @@ function memoizeGetPort(defaultPort: number) {
596
547
  return portValue || (portValue = await getPort({ port: defaultPort }));
597
548
  };
598
549
  }
550
+
551
+ async function getBindings(
552
+ configParam: Config,
553
+ args: AdditionalDevProps
554
+ ): Promise<CfWorkerInit["bindings"]> {
555
+ const bindings = {
556
+ kv_namespaces: [
557
+ ...(configParam.kv_namespaces || []).map(
558
+ ({ binding, preview_id, id: _id }) => {
559
+ // In `dev`, we make folks use a separate kv namespace called
560
+ // `preview_id` instead of `id` so that they don't
561
+ // break production data. So here we check that a `preview_id`
562
+ // has actually been configured.
563
+ // This whole block of code will be obsoleted in the future
564
+ // when we have copy-on-write for previews on edge workers.
565
+ if (!preview_id) {
566
+ // TODO: This error has to be a _lot_ better, ideally just asking
567
+ // to create a preview namespace for the user automatically
568
+ throw new Error(
569
+ `In development, you should use a separate kv namespace than the one you'd use in production. Please create a new kv namespace with "wrangler kv:namespace create <name> --preview" and add its id as preview_id to the kv_namespace "${binding}" in your wrangler.toml`
570
+ ); // Ugh, I really don't like this message very much
571
+ }
572
+ return {
573
+ binding,
574
+ id: preview_id,
575
+ };
576
+ }
577
+ ),
578
+ ...(args.kv || []),
579
+ ],
580
+ // Use a copy of combinedVars since we're modifying it later
581
+ vars: {
582
+ ...getVarsForDev(configParam),
583
+ ...args.vars,
584
+ },
585
+ wasm_modules: configParam.wasm_modules,
586
+ text_blobs: configParam.text_blobs,
587
+ data_blobs: configParam.data_blobs,
588
+ durable_objects: {
589
+ bindings: [
590
+ ...(configParam.durable_objects || { bindings: [] }).bindings,
591
+ ...(args.durableObjects || []),
592
+ ],
593
+ },
594
+ r2_buckets: configParam.r2_buckets?.map(
595
+ ({ binding, preview_bucket_name, bucket_name: _bucket_name }) => {
596
+ // same idea as kv namespace preview id,
597
+ // same copy-on-write TODO
598
+ if (!preview_bucket_name) {
599
+ throw new Error(
600
+ `In development, you should use a separate r2 bucket than the one you'd use in production. Please create a new r2 bucket with "wrangler r2 bucket create <name>" and add its name as preview_bucket_name to the r2_buckets "${binding}" in your wrangler.toml`
601
+ );
602
+ }
603
+ return {
604
+ binding,
605
+ bucket_name: preview_bucket_name,
606
+ };
607
+ }
608
+ ),
609
+ worker_namespaces: configParam.worker_namespaces,
610
+ services: configParam.services,
611
+ unsafe: configParam.unsafe?.bindings,
612
+ logfwdr: configParam.logfwdr,
613
+ };
614
+
615
+ return bindings;
616
+ }
package/src/generate.ts CHANGED
@@ -1,33 +1,131 @@
1
- import { DeprecationError } from "./errors";
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ import { setup as createCloudflare } from "create-cloudflare";
4
+ import { initHandler } from "./init";
5
+ import { logger } from "./logger";
6
+ import { CommandLineArgsError, printWranglerBanner } from ".";
2
7
  import type { Argv, ArgumentsCamelCase } from "yargs";
3
8
 
9
+ // https://github.com/cloudflare/wrangler/blob/master/src/cli/mod.rs#L106-L123
4
10
  interface GenerateArgs {
5
- name: string;
6
- template: string;
11
+ name?: string;
12
+ template?: string;
13
+ type?: string;
14
+ site?: boolean;
7
15
  }
8
16
 
9
17
  export function generateOptions(yargs: Argv) {
10
18
  return yargs
11
19
  .positional("name", {
12
20
  describe: "Name of the Workers project",
13
- default: "worker",
21
+ type: "string",
14
22
  })
15
23
  .positional("template", {
24
+ type: "string",
16
25
  describe: "The URL of a GitHub template",
17
- default: "https://github.com/cloudflare/worker-template",
26
+ })
27
+ .option("type", {
28
+ alias: "t",
29
+ type: "string",
30
+ hidden: true,
31
+ deprecated: true,
32
+ })
33
+ .option("site", {
34
+ alias: "s",
35
+ type: "boolean",
36
+ hidden: true,
37
+ deprecated: true,
18
38
  });
19
39
  }
20
40
 
21
- export function generateHandler(
22
- generateArgs: ArgumentsCamelCase<GenerateArgs>
23
- ) {
24
- // "👯 [DEPRECATED]. Scaffold a Cloudflare Workers project from a public GitHub repository.",
25
- throw new DeprecationError(
26
- "`wrangler generate` has been deprecated.\n" +
27
- "Try running `wrangler init` to generate a basic Worker, or cloning the template repository instead:\n\n" +
41
+ export async function generateHandler({
42
+ // somehow, `init` marks name as required but then also runs fine
43
+ // with the name omitted, and then substitutes it at runtime with ""
44
+ name = "",
45
+ template,
46
+ type,
47
+ site,
48
+ ...args
49
+ }: ArgumentsCamelCase<GenerateArgs>) {
50
+ // delegate to `wrangler init` if no template is specified
51
+ if (template === undefined) {
52
+ return initHandler({ name, ...args });
53
+ }
54
+
55
+ // print down here cuz `init` prints it own its own
56
+ printWranglerBanner();
57
+
58
+ if (type) {
59
+ let message = "The --type option is no longer supported.";
60
+ if (args.type === "webpack") {
61
+ message +=
62
+ "\nIf you wish to use webpack then you will need to create a custom build.";
63
+ // TODO: Add a link to docs
64
+ }
65
+ throw new CommandLineArgsError(message);
66
+ }
67
+
68
+ const creationDirectory = generateWorkerDirectoryName(name);
69
+
70
+ if (site) {
71
+ const gitDirectory =
72
+ creationDirectory !== process.cwd()
73
+ ? path.basename(creationDirectory)
74
+ : "my-site";
75
+ const message =
76
+ "The --site option is no longer supported.\n" +
77
+ "If you wish to create a brand new Worker Sites project then clone the `worker-sites-template` starter repository:\n\n" +
28
78
  "```\n" +
29
- `git clone ${generateArgs.template}\n` +
79
+ `git clone --depth=1 --branch=wrangler2 https://github.com/cloudflare/worker-sites-template ${gitDirectory}\n` +
80
+ `cd ${gitDirectory}\n` +
30
81
  "```\n\n" +
31
- "Please refer to https://developers.cloudflare.com/workers/wrangler/deprecations/#generate for more information."
82
+ "Find out more about how to create and maintain Sites projects at https://developers.cloudflare.com/workers/platform/sites.\n" +
83
+ "Have you considered using Cloudflare Pages instead? See https://pages.cloudflare.com/.";
84
+ throw new CommandLineArgsError(message);
85
+ }
86
+
87
+ logger.log(
88
+ `Creating a worker in ${path.basename(creationDirectory)} from ${template}`
32
89
  );
90
+
91
+ await createCloudflare(creationDirectory, template, {
92
+ init: true, // initialize a git repository
93
+ debug: logger.loggerLevel === "debug",
94
+ force: false, // do not overwrite an existing directory
95
+ });
96
+ }
97
+
98
+ /**
99
+ * Creates a path based on the current working directory and a worker name.
100
+ * Automatically increments a counter when searching for an available directory.
101
+ *
102
+ * Running `wrangler generate worker https://some-git-repo` in a directory
103
+ * with the structure:
104
+ * ```
105
+ * - workers
106
+ * |
107
+ * | - worker
108
+ * | | - wrangler.toml
109
+ * | | ...
110
+ * |
111
+ * | - worker-1
112
+ * | | - wrangler.toml
113
+ * | | ...
114
+ * ```
115
+ *
116
+ * will result in a new worker called `worker-2` being generated.
117
+ *
118
+ * @param workerName the name of the generated worker
119
+ * @returns an absolute path to the directory to generate the worker into
120
+ */
121
+ function generateWorkerDirectoryName(workerName: string): string {
122
+ let workerDirectoryPath = path.resolve(process.cwd(), workerName);
123
+ let i = 1;
124
+
125
+ while (fs.existsSync(workerDirectoryPath)) {
126
+ workerDirectoryPath = path.resolve(process.cwd(), `${workerName}-${i}`);
127
+ i++;
128
+ }
129
+
130
+ return workerDirectoryPath;
33
131
  }