wrangler 0.0.0-e22f9d7 → 0.0.0-e2b7352
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -3
- package/bin/wrangler.js +12 -3
- package/miniflare-dist/index.mjs +79 -0
- package/miniflare-dist/index.mjs.map +7 -0
- package/package.json +33 -16
- package/pages/functions/buildWorker.ts +1 -1
- package/pages/functions/filepath-routing.test.ts +212 -28
- package/pages/functions/filepath-routing.ts +91 -131
- package/pages/functions/routes.ts +11 -18
- package/pages/functions/template-worker.ts +19 -33
- package/src/__tests__/configuration.test.ts +2820 -0
- package/src/__tests__/dev.test.tsx +535 -61
- package/src/__tests__/guess-worker-format.test.ts +68 -0
- package/src/__tests__/{clipboardy-mock.js → helpers/clipboardy-mock.js} +0 -0
- package/src/__tests__/helpers/cmd-shim.d.ts +11 -0
- package/src/__tests__/helpers/faye-websocket.d.ts +6 -0
- package/src/__tests__/helpers/mock-account-id.ts +48 -0
- package/src/__tests__/helpers/mock-bin.ts +36 -0
- package/src/__tests__/{mock-cfetch.ts → helpers/mock-cfetch.ts} +52 -16
- package/src/__tests__/helpers/mock-console.ts +70 -0
- package/src/__tests__/{mock-dialogs.ts → helpers/mock-dialogs.ts} +1 -1
- package/src/__tests__/helpers/mock-kv.ts +33 -0
- package/src/__tests__/helpers/mock-stdin.ts +103 -0
- package/src/__tests__/helpers/mock-web-socket.ts +29 -0
- package/src/__tests__/helpers/run-in-tmp.ts +32 -0
- package/src/__tests__/helpers/run-wrangler.ts +16 -0
- package/src/__tests__/helpers/write-wrangler-toml.ts +17 -0
- package/src/__tests__/https-options.test.ts +127 -0
- package/src/__tests__/index.test.ts +773 -77
- package/src/__tests__/jest.setup.ts +53 -6
- package/src/__tests__/kv.test.ts +725 -456
- package/src/__tests__/logout.test.ts +52 -0
- package/src/__tests__/package-manager.test.ts +206 -0
- package/src/__tests__/pages.test.ts +82 -0
- package/src/__tests__/parse.test.ts +322 -0
- package/src/__tests__/publish.test.ts +3509 -0
- package/src/__tests__/r2.test.ts +198 -0
- package/src/__tests__/route.test.ts +45 -0
- package/src/__tests__/secret.test.ts +425 -0
- package/src/__tests__/sentry.test.ts +146 -0
- package/src/__tests__/tail.test.ts +638 -0
- package/src/__tests__/whoami.test.tsx +100 -0
- package/src/bundle.ts +155 -0
- package/src/cfetch/index.ts +21 -19
- package/src/cfetch/internal.ts +92 -22
- package/src/cli.ts +10 -0
- package/src/config/README.md +107 -0
- package/src/config/config.ts +218 -0
- package/src/config/diagnostics.ts +80 -0
- package/src/config/environment.ts +371 -0
- package/src/config/index.ts +62 -0
- package/src/config/validation-helpers.ts +554 -0
- package/src/config/validation.ts +1232 -0
- package/src/{api/preview.ts → create-worker-preview.ts} +61 -23
- package/src/create-worker-upload-form.ts +217 -0
- package/src/dev/dev.tsx +392 -0
- package/src/dev/local.tsx +232 -0
- package/src/dev/remote.tsx +203 -0
- package/src/dev/use-esbuild.ts +104 -0
- package/src/dialogs.tsx +2 -2
- package/src/entry.ts +240 -0
- package/src/environment-variables.ts +33 -0
- package/src/https-options.ts +122 -0
- package/src/index.tsx +1218 -803
- package/src/inspect.ts +103 -25
- package/src/intl-polyfill.d.ts +139 -0
- package/src/{kv.tsx → kv.ts} +104 -61
- package/src/miniflare-cli/README.md +30 -0
- package/src/miniflare-cli/enum-keys.ts +17 -0
- package/src/miniflare-cli/index.ts +37 -0
- package/src/module-collection.ts +229 -35
- package/src/open-in-browser.ts +25 -0
- package/src/package-manager.ts +126 -0
- package/src/pages.tsx +74 -44
- package/src/parse.ts +183 -0
- package/src/paths.ts +26 -0
- package/src/proxy.ts +177 -39
- package/src/publish.ts +286 -300
- package/src/r2.ts +50 -0
- package/src/reporting.ts +131 -0
- package/src/selfsigned.d.ts +29 -0
- package/src/sites.tsx +224 -62
- package/src/tail/filters.ts +279 -0
- package/src/tail/index.ts +299 -0
- package/src/tail/printing.ts +62 -0
- package/src/user.tsx +278 -178
- package/src/whoami.tsx +69 -0
- package/src/{api/worker.ts → worker.ts} +45 -37
- package/templates/new-worker.js +15 -0
- package/templates/new-worker.ts +15 -0
- package/{static-asset-facade.js → templates/static-asset-facade.js} +0 -0
- package/templates/tsconfig.json +105 -0
- package/wrangler-dist/cli.js +130377 -112126
- package/wrangler-dist/cli.js.map +3 -3
- package/src/__tests__/run-in-tmp.ts +0 -19
- package/src/__tests__/run-wrangler.ts +0 -32
- package/src/api/form_data.ts +0 -131
- package/src/config.ts +0 -487
- package/src/dev.tsx +0 -784
- package/src/tail.tsx +0 -73
package/src/pages.tsx
CHANGED
|
@@ -1,32 +1,36 @@
|
|
|
1
1
|
/* eslint-disable no-shadow */
|
|
2
2
|
|
|
3
|
-
import
|
|
4
|
-
import
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import { execSync, spawn } from "child_process";
|
|
9
|
-
import { URL } from "url";
|
|
10
|
-
import { getType } from "mime";
|
|
11
|
-
import open from "open";
|
|
3
|
+
import { execSync, spawn } from "node:child_process";
|
|
4
|
+
import { existsSync, lstatSync, readFileSync, writeFileSync } from "node:fs";
|
|
5
|
+
import { tmpdir } from "node:os";
|
|
6
|
+
import { join } from "node:path";
|
|
7
|
+
import { URL } from "node:url";
|
|
12
8
|
import { watch } from "chokidar";
|
|
13
|
-
import
|
|
9
|
+
import { getType } from "mime";
|
|
14
10
|
import { buildWorker } from "../pages/functions/buildWorker";
|
|
15
|
-
import type { Config } from "../pages/functions/routes";
|
|
16
|
-
import { writeRoutesModule } from "../pages/functions/routes";
|
|
17
11
|
import { generateConfigFromFileTree } from "../pages/functions/filepath-routing";
|
|
12
|
+
import { writeRoutesModule } from "../pages/functions/routes";
|
|
13
|
+
import openInBrowser from "./open-in-browser";
|
|
14
|
+
import { toUrlPath } from "./paths";
|
|
15
|
+
import type { Config } from "../pages/functions/routes";
|
|
16
|
+
import type { Headers, Request, fetch } from "@miniflare/core";
|
|
17
|
+
import type { BuildResult } from "esbuild";
|
|
18
|
+
import type { MiniflareOptions } from "miniflare";
|
|
19
|
+
import type { BuilderCallback } from "yargs";
|
|
18
20
|
|
|
19
21
|
// Defer importing miniflare until we really need it. This takes ~0.5s
|
|
20
22
|
// and also modifies some `stream/web` and `undici` prototypes, so we
|
|
21
23
|
// don't want to do this if pages commands aren't being called.
|
|
22
|
-
import type { Headers, Request, fetch } from "@miniflare/core";
|
|
23
|
-
import type { MiniflareOptions } from "miniflare";
|
|
24
24
|
|
|
25
|
-
const
|
|
25
|
+
export const pagesBetaWarning =
|
|
26
|
+
"🚧 'wrangler pages <command>' is a beta command. Please report any issues to https://github.com/cloudflare/wrangler2/issues/new/choose";
|
|
27
|
+
|
|
28
|
+
const EXIT_CALLBACKS: (() => void)[] = [];
|
|
26
29
|
const EXIT = (message?: string, code?: number) => {
|
|
27
30
|
if (message) console.log(message);
|
|
28
31
|
if (code) process.exitCode = code;
|
|
29
32
|
EXIT_CALLBACKS.forEach((callback) => callback());
|
|
33
|
+
RUNNING_BUILDERS.forEach((builder) => builder.stop?.());
|
|
30
34
|
process.exit(code);
|
|
31
35
|
};
|
|
32
36
|
|
|
@@ -122,7 +126,9 @@ async function spawnProxyProcess({
|
|
|
122
126
|
},
|
|
123
127
|
}
|
|
124
128
|
);
|
|
125
|
-
EXIT_CALLBACKS.push(() =>
|
|
129
|
+
EXIT_CALLBACKS.push(() => {
|
|
130
|
+
proxy.kill();
|
|
131
|
+
});
|
|
126
132
|
|
|
127
133
|
proxy.stdout.on("data", (data) => {
|
|
128
134
|
console.log(`[proxy]: ${data}`);
|
|
@@ -664,16 +670,17 @@ async function buildFunctions({
|
|
|
664
670
|
);
|
|
665
671
|
|
|
666
672
|
const routesModule = join(tmpdir(), "./functionsRoutes.mjs");
|
|
673
|
+
const baseURL = toUrlPath("/");
|
|
667
674
|
|
|
668
675
|
const config: Config = await generateConfigFromFileTree({
|
|
669
676
|
baseDir: functionsDirectory,
|
|
670
|
-
baseURL
|
|
677
|
+
baseURL,
|
|
671
678
|
});
|
|
672
679
|
|
|
673
680
|
if (outputConfigPath) {
|
|
674
681
|
writeFileSync(
|
|
675
682
|
outputConfigPath,
|
|
676
|
-
JSON.stringify({ ...config, baseURL
|
|
683
|
+
JSON.stringify({ ...config, baseURL }, null, 2)
|
|
677
684
|
);
|
|
678
685
|
}
|
|
679
686
|
|
|
@@ -756,7 +763,8 @@ export const pages: BuilderCallback<unknown, unknown> = (yargs) => {
|
|
|
756
763
|
description: "Auto reload HTML pages when change is detected",
|
|
757
764
|
},
|
|
758
765
|
// TODO: Miniflare user options
|
|
759
|
-
})
|
|
766
|
+
})
|
|
767
|
+
.epilogue(pagesBetaWarning);
|
|
760
768
|
},
|
|
761
769
|
async ({
|
|
762
770
|
local,
|
|
@@ -768,8 +776,11 @@ export const pages: BuilderCallback<unknown, unknown> = (yargs) => {
|
|
|
768
776
|
kv: kvs = [],
|
|
769
777
|
do: durableObjects = [],
|
|
770
778
|
"live-reload": liveReload,
|
|
771
|
-
|
|
779
|
+
_: [_pages, _dev, ...remaining],
|
|
772
780
|
}) => {
|
|
781
|
+
// Beta message for `wrangler pages <commands>` usage
|
|
782
|
+
console.log(pagesBetaWarning);
|
|
783
|
+
|
|
773
784
|
if (!local) {
|
|
774
785
|
console.error("Only local mode is supported at the moment.");
|
|
775
786
|
return;
|
|
@@ -792,8 +803,8 @@ export const pages: BuilderCallback<unknown, unknown> = (yargs) => {
|
|
|
792
803
|
|
|
793
804
|
let miniflareArgs: MiniflareOptions = {};
|
|
794
805
|
|
|
795
|
-
let scriptReadyResolve;
|
|
796
|
-
const scriptReadyPromise = new Promise(
|
|
806
|
+
let scriptReadyResolve: () => void;
|
|
807
|
+
const scriptReadyPromise = new Promise<void>(
|
|
797
808
|
(resolve) => (scriptReadyResolve = resolve)
|
|
798
809
|
);
|
|
799
810
|
|
|
@@ -802,13 +813,15 @@ export const pages: BuilderCallback<unknown, unknown> = (yargs) => {
|
|
|
802
813
|
|
|
803
814
|
console.log(`Compiling worker to "${scriptPath}"...`);
|
|
804
815
|
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
816
|
+
try {
|
|
817
|
+
await buildFunctions({
|
|
818
|
+
scriptPath,
|
|
819
|
+
functionsDirectory,
|
|
820
|
+
sourcemap: true,
|
|
821
|
+
watch: true,
|
|
822
|
+
onEnd: () => scriptReadyResolve(),
|
|
823
|
+
});
|
|
824
|
+
} catch {}
|
|
812
825
|
|
|
813
826
|
watch([functionsDirectory], {
|
|
814
827
|
persistent: true,
|
|
@@ -827,6 +840,9 @@ export const pages: BuilderCallback<unknown, unknown> = (yargs) => {
|
|
|
827
840
|
scriptPath,
|
|
828
841
|
};
|
|
829
842
|
} else {
|
|
843
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
844
|
+
scriptReadyResolve!();
|
|
845
|
+
|
|
830
846
|
const scriptPath =
|
|
831
847
|
directory !== undefined
|
|
832
848
|
? join(directory, singleWorkerScriptPath)
|
|
@@ -862,11 +878,13 @@ export const pages: BuilderCallback<unknown, unknown> = (yargs) => {
|
|
|
862
878
|
// internally just waits for that promise to resolve.
|
|
863
879
|
await scriptReadyPromise;
|
|
864
880
|
|
|
865
|
-
//
|
|
866
|
-
//
|
|
867
|
-
const assetsFetch =
|
|
868
|
-
|
|
869
|
-
|
|
881
|
+
// `assetsFetch()` will only be called if there is `proxyPort` defined.
|
|
882
|
+
// We only define `proxyPort`, above, when there is no `directory` defined.
|
|
883
|
+
const assetsFetch =
|
|
884
|
+
directory !== undefined
|
|
885
|
+
? await generateAssetsFetch(directory)
|
|
886
|
+
: invalidAssetsFetch;
|
|
887
|
+
|
|
870
888
|
const miniflare = new Miniflare({
|
|
871
889
|
port,
|
|
872
890
|
watch: true,
|
|
@@ -887,17 +905,19 @@ export const pages: BuilderCallback<unknown, unknown> = (yargs) => {
|
|
|
887
905
|
// User bindings
|
|
888
906
|
bindings: {
|
|
889
907
|
...Object.fromEntries(
|
|
890
|
-
bindings
|
|
908
|
+
bindings
|
|
909
|
+
.map((binding) => binding.toString().split("="))
|
|
910
|
+
.map(([key, ...values]) => [key, values.join("=")])
|
|
891
911
|
),
|
|
892
912
|
},
|
|
893
913
|
|
|
894
914
|
// env.ASSETS.fetch
|
|
895
915
|
serviceBindings: {
|
|
896
|
-
async ASSETS(request) {
|
|
916
|
+
async ASSETS(request: Request) {
|
|
897
917
|
if (proxyPort) {
|
|
898
918
|
try {
|
|
899
919
|
const url = new URL(request.url);
|
|
900
|
-
url.host = `
|
|
920
|
+
url.host = `localhost:${proxyPort}`;
|
|
901
921
|
return await fetch(url, request);
|
|
902
922
|
} catch (thrown) {
|
|
903
923
|
console.error(`Could not proxy request: ${thrown}`);
|
|
@@ -935,12 +955,10 @@ export const pages: BuilderCallback<unknown, unknown> = (yargs) => {
|
|
|
935
955
|
try {
|
|
936
956
|
// `startServer` might throw if user code contains errors
|
|
937
957
|
const server = await miniflare.startServer();
|
|
938
|
-
console.log(`Serving at http://
|
|
958
|
+
console.log(`Serving at http://localhost:${port}/`);
|
|
939
959
|
|
|
940
960
|
if (process.env.BROWSER !== "none") {
|
|
941
|
-
|
|
942
|
-
// fail silently if the open command doesn't work (e.g. in GitHub Codespaces)
|
|
943
|
-
childProcess.on("error", (_err) => {});
|
|
961
|
+
await openInBrowser(`http://localhost:${port}/`);
|
|
944
962
|
}
|
|
945
963
|
|
|
946
964
|
if (directory !== undefined && liveReload) {
|
|
@@ -957,12 +975,14 @@ export const pages: BuilderCallback<unknown, unknown> = (yargs) => {
|
|
|
957
975
|
miniflare.dispose().catch((err) => miniflare.log.error(err));
|
|
958
976
|
});
|
|
959
977
|
} catch (e) {
|
|
960
|
-
miniflare.log.error(e);
|
|
978
|
+
miniflare.log.error(e as Error);
|
|
961
979
|
EXIT("Could not start Miniflare.", 1);
|
|
962
980
|
}
|
|
963
981
|
}
|
|
964
982
|
)
|
|
965
|
-
.command("functions",
|
|
983
|
+
.command("functions", false, (yargs) =>
|
|
984
|
+
// we hide this command from help output because
|
|
985
|
+
// it's not meant to be used directly right now
|
|
966
986
|
yargs.command(
|
|
967
987
|
"build [directory]",
|
|
968
988
|
"Compile a folder of Cloudflare Pages Functions into a single Worker",
|
|
@@ -1006,7 +1026,8 @@ export const pages: BuilderCallback<unknown, unknown> = (yargs) => {
|
|
|
1006
1026
|
description:
|
|
1007
1027
|
"Watch for changes to the functions and automatically rebuild the Worker script",
|
|
1008
1028
|
},
|
|
1009
|
-
})
|
|
1029
|
+
})
|
|
1030
|
+
.epilogue(pagesBetaWarning),
|
|
1010
1031
|
async ({
|
|
1011
1032
|
directory,
|
|
1012
1033
|
"script-path": scriptPath,
|
|
@@ -1016,6 +1037,9 @@ export const pages: BuilderCallback<unknown, unknown> = (yargs) => {
|
|
|
1016
1037
|
fallbackService,
|
|
1017
1038
|
watch,
|
|
1018
1039
|
}) => {
|
|
1040
|
+
// Beta message for `wrangler pages <commands>` usage
|
|
1041
|
+
console.log(pagesBetaWarning);
|
|
1042
|
+
|
|
1019
1043
|
await buildFunctions({
|
|
1020
1044
|
scriptPath,
|
|
1021
1045
|
outputConfigPath,
|
|
@@ -1029,3 +1053,9 @@ export const pages: BuilderCallback<unknown, unknown> = (yargs) => {
|
|
|
1029
1053
|
)
|
|
1030
1054
|
);
|
|
1031
1055
|
};
|
|
1056
|
+
|
|
1057
|
+
const invalidAssetsFetch: typeof fetch = () => {
|
|
1058
|
+
throw new Error(
|
|
1059
|
+
"Trying to fetch assets directly when there is no `directory` option specified, and not in `local` mode."
|
|
1060
|
+
);
|
|
1061
|
+
};
|
package/src/parse.ts
ADDED
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
import * as fs from "node:fs";
|
|
2
|
+
import { resolve } from "node:path";
|
|
3
|
+
import TOML from "@iarna/toml";
|
|
4
|
+
import { formatMessagesSync } from "esbuild";
|
|
5
|
+
|
|
6
|
+
export type Message = {
|
|
7
|
+
text: string;
|
|
8
|
+
location?: Location;
|
|
9
|
+
notes?: Message[];
|
|
10
|
+
kind?: "warning" | "error";
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export type Location = File & {
|
|
14
|
+
line: number;
|
|
15
|
+
column: number;
|
|
16
|
+
length?: number;
|
|
17
|
+
lineText?: string;
|
|
18
|
+
suggestion?: string;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
export type File = {
|
|
22
|
+
file?: string;
|
|
23
|
+
fileText?: string;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Formats a `Message` using esbuild's pretty-printing algorithm.
|
|
28
|
+
*/
|
|
29
|
+
export function formatMessage(
|
|
30
|
+
{ text, notes, location, kind = "error" }: Message,
|
|
31
|
+
color = true
|
|
32
|
+
): string {
|
|
33
|
+
const input = { text, notes, location };
|
|
34
|
+
delete input.location?.fileText;
|
|
35
|
+
for (const note of notes ?? []) {
|
|
36
|
+
delete note.location?.fileText;
|
|
37
|
+
}
|
|
38
|
+
const lines = formatMessagesSync([input], {
|
|
39
|
+
color,
|
|
40
|
+
kind: kind,
|
|
41
|
+
terminalWidth: process.stderr.columns,
|
|
42
|
+
});
|
|
43
|
+
return lines.join("\n");
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* An error that's thrown when something fails to parse.
|
|
48
|
+
*/
|
|
49
|
+
export class ParseError extends Error implements Message {
|
|
50
|
+
readonly text: string;
|
|
51
|
+
readonly notes: Message[];
|
|
52
|
+
readonly location?: Location;
|
|
53
|
+
readonly kind: "warning" | "error";
|
|
54
|
+
|
|
55
|
+
constructor({ text, notes, location, kind }: Message) {
|
|
56
|
+
super(text);
|
|
57
|
+
this.name = this.constructor.name;
|
|
58
|
+
this.text = text;
|
|
59
|
+
this.notes = notes ?? [];
|
|
60
|
+
this.location = location;
|
|
61
|
+
this.kind = kind ?? "error";
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
66
|
+
|
|
67
|
+
const TOML_ERROR_NAME = "TomlError";
|
|
68
|
+
const TOML_ERROR_SUFFIX = " at row ";
|
|
69
|
+
|
|
70
|
+
type TomlError = Error & {
|
|
71
|
+
line: number;
|
|
72
|
+
col: number;
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* A wrapper around `TOML.parse` that throws a `ParseError`.
|
|
77
|
+
*/
|
|
78
|
+
export function parseTOML(input: string, file?: string): object {
|
|
79
|
+
try {
|
|
80
|
+
return TOML.parse(input);
|
|
81
|
+
} catch (err) {
|
|
82
|
+
const { name, message, line, col } = err as TomlError;
|
|
83
|
+
if (name !== TOML_ERROR_NAME) {
|
|
84
|
+
throw err;
|
|
85
|
+
}
|
|
86
|
+
const text = message.substring(0, message.lastIndexOf(TOML_ERROR_SUFFIX));
|
|
87
|
+
const lineText = input.split("\n")[line];
|
|
88
|
+
const location = {
|
|
89
|
+
lineText,
|
|
90
|
+
line: line + 1,
|
|
91
|
+
column: col - 1,
|
|
92
|
+
file,
|
|
93
|
+
fileText: input,
|
|
94
|
+
};
|
|
95
|
+
throw new ParseError({ text, location });
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const JSON_ERROR_SUFFIX = " in JSON at position ";
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* A wrapper around `JSON.parse` that throws a `ParseError`.
|
|
103
|
+
*/
|
|
104
|
+
export function parseJSON(input: string, file?: string): any {
|
|
105
|
+
try {
|
|
106
|
+
return JSON.parse(input);
|
|
107
|
+
} catch (err) {
|
|
108
|
+
const { message } = err as Error;
|
|
109
|
+
const index = message.lastIndexOf(JSON_ERROR_SUFFIX);
|
|
110
|
+
if (index < 0) {
|
|
111
|
+
throw err;
|
|
112
|
+
}
|
|
113
|
+
const text = message.substring(0, index);
|
|
114
|
+
const position = parseInt(
|
|
115
|
+
message.substring(index + JSON_ERROR_SUFFIX.length)
|
|
116
|
+
);
|
|
117
|
+
const location = indexLocation({ file, fileText: input }, position);
|
|
118
|
+
throw new ParseError({ text, location });
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Reads a file and parses it based on its type.
|
|
124
|
+
*/
|
|
125
|
+
export function readFileSync(file: string): string {
|
|
126
|
+
try {
|
|
127
|
+
return fs.readFileSync(file, { encoding: "utf-8" });
|
|
128
|
+
} catch (err) {
|
|
129
|
+
const { message } = err as Error;
|
|
130
|
+
throw new ParseError({
|
|
131
|
+
text: `Could not read file: ${file}`,
|
|
132
|
+
notes: [
|
|
133
|
+
{
|
|
134
|
+
text: message.replace(file, resolve(file)),
|
|
135
|
+
},
|
|
136
|
+
],
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Calculates the line and column location from an index.
|
|
143
|
+
*/
|
|
144
|
+
export function indexLocation(file: File, index: number): Location {
|
|
145
|
+
let lineText,
|
|
146
|
+
line = 0,
|
|
147
|
+
column = 0,
|
|
148
|
+
cursor = 0;
|
|
149
|
+
const { fileText = "" } = file;
|
|
150
|
+
for (const row of fileText.split("\n")) {
|
|
151
|
+
line++;
|
|
152
|
+
cursor += row.length + 1;
|
|
153
|
+
if (cursor >= index) {
|
|
154
|
+
lineText = row;
|
|
155
|
+
column = row.length - (cursor - index);
|
|
156
|
+
break;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
return { lineText, line, column, ...file };
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Guesses the line and column location of a search query.
|
|
164
|
+
*/
|
|
165
|
+
export function searchLocation(file: File, query: unknown): Location {
|
|
166
|
+
let lineText,
|
|
167
|
+
length,
|
|
168
|
+
line = 0,
|
|
169
|
+
column = 0;
|
|
170
|
+
const queryText = String(query);
|
|
171
|
+
const { fileText = "" } = file;
|
|
172
|
+
for (const content of fileText.split("\n")) {
|
|
173
|
+
line++;
|
|
174
|
+
const index = content.indexOf(queryText);
|
|
175
|
+
if (index >= 0) {
|
|
176
|
+
lineText = content;
|
|
177
|
+
column = index;
|
|
178
|
+
length = queryText.length;
|
|
179
|
+
break;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
return { lineText, line, column, length, ...file };
|
|
183
|
+
}
|
package/src/paths.ts
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { assert } from "console";
|
|
2
|
+
|
|
3
|
+
type DiscriminatedPath<Discriminator extends string> = string & {
|
|
4
|
+
_discriminator: Discriminator;
|
|
5
|
+
};
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* A branded string that expects to be URL compatible.
|
|
9
|
+
*
|
|
10
|
+
* Require this type when you want callers to ensure that they have converted file-path strings into URL-safe paths.
|
|
11
|
+
*/
|
|
12
|
+
export type UrlPath = DiscriminatedPath<"UrlPath">;
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Convert a file-path string to a URL-path string.
|
|
16
|
+
*
|
|
17
|
+
* Use this helper to convert a `string` to a `UrlPath` when it is not clear whether the string needs normalizing.
|
|
18
|
+
* Replaces all back-slashes with forward-slashes, and throws an error if the path contains a drive letter (e.g. `C:`).
|
|
19
|
+
*/
|
|
20
|
+
export function toUrlPath(path: string): UrlPath {
|
|
21
|
+
assert(
|
|
22
|
+
!/^[a-z]:/i.test(path),
|
|
23
|
+
"Tried to convert a Windows file path with a drive to a URL path."
|
|
24
|
+
);
|
|
25
|
+
return path.replace(/\\/g, "/") as UrlPath;
|
|
26
|
+
}
|