@xlameiro/env-typegen 0.1.1 → 0.1.2

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/CHANGELOG.md CHANGED
@@ -1,5 +1,11 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.1.2
4
+
5
+ ### Patch Changes
6
+
7
+ - Refresh the published npm package metadata and README to match the current repository documentation.
8
+
3
9
  All notable changes to this project will be documented in this file.
4
10
 
5
11
  This project adheres to [Semantic Versioning](https://semver.org/) and uses
@@ -7,7 +13,39 @@ This project adheres to [Semantic Versioning](https://semver.org/) and uses
7
13
 
8
14
  <!-- Releases are added automatically by `changeset version` -->
9
15
 
10
- ## [0.1.0] — 2026-03-16
16
+ ## [0.1.1] — 2026-03-17
17
+
18
+ ### Fixed
19
+
20
+ - **A1** — VERSION was hardcoded as `"0.1.0"` in `cli.ts`; now read dynamically from `package.json` via `createRequire` so it stays in sync without manual edits.
21
+ - **A2** — Double-quotes in `@description` annotations were not escaped in the t3-generator output, corrupting the generated `createEnv(...)` call. Fixed with `.replace(/"/g, '\\"')`.
22
+ - **A3** — Prettier parse errors in `formatOutput` were silently swallowed; a `console.warn` is now emitted before the raw-content fallback.
23
+ - **A4** — Watch mode only listened for `"change"` events; it now also handles `"add"` and `"unlink"` so editor save-with-delete patterns are covered.
24
+ - **A5** — Config file changes during `--watch` were silently ignored; a second chokidar watcher now monitors the config file and reloads it on change.
25
+ - **C1** — `formatOutput` (Prettier) was called unnecessarily in dry-run mode; formatting now runs only when actually writing to disk.
26
+ - **C3** — Removed duplicate alias exports (`generateTypeScript`, `generateZod`); the canonical names `generateTypeScriptTypes` and `generateZodSchema` are the only public exports.
27
+ - **E1–E3** — README badge URLs had `YOUR_USERNAME` placeholders; replaced with the real GitHub/npm identifiers. Package name aligned to `@xlameiro/env-typegen` throughout all docs.
28
+ - **E4** — README `--format` example was misleading; corrected to use `--generator`/`-f` for generator selection and document `--no-format` for disabling Prettier.
29
+ - **E5** — `parseEnvFile` was documented as `async`/awaitable but is synchronous; removed erroneous `await` from all examples.
30
+ - **DRY** — `CONFIG_FILE_NAMES` was duplicated between `config.ts` and `watch.ts`; exported from `config.ts` and imported in `watch.ts`.
31
+
32
+ ### Added
33
+
34
+ - **B1** — `EnvTypegenConfig.input` now accepts `string | string[]`; CLI `--input`/`-i` can be repeated to process multiple `.env` files in one run.
35
+ - **B2** — `inferenceRules?: InferenceRule[]` added to `EnvTypegenConfig` and `RunGenerateOptions`; custom rules are prepended before the built-in ruleset.
36
+ - **B3** — Default generators changed from `["typescript"]` to all four: `["typescript", "zod", "t3", "declaration"]`, matching the spec.
37
+ - **B4** — Dry-run mode now prints each generated output block to stdout so users can preview content without touching the filesystem.
38
+ - **B5** — Watch mode debounces rapid file-change events (200 ms) to prevent multiple simultaneous pipeline runs on editor autosave.
39
+
40
+ ### Tests
41
+
42
+ - Regression test for double-quotes in t3-generator descriptions (A2).
43
+ - CLI version tests read expected version from `package.json` (A1).
44
+ - Four `loadConfig()` unit tests with temp-dir fixtures (no stale state).
45
+ - Dry-run stdout spy test verifying preview content appears (B4).
46
+ - Integration tests expanded to five end-to-end scenarios via `execSync` against the built `dist/cli.js`.
47
+
48
+ [0.1.1]: https://github.com/xlameiro/env-typegen/compare/packages/env-typegen@0.1.0...packages/env-typegen@0.1.1
11
49
 
12
50
  ### Added
13
51
 
@@ -54,4 +92,4 @@ This project adheres to [Semantic Versioning](https://semver.org/) and uses
54
92
  - Full TSDoc on every public export in `src/index.ts`
55
93
  - README with Quick Start (CLI examples), Programmatic API, and Configuration sections
56
94
 
57
- [0.1.0]: https://github.com/YOUR_USERNAME/env-typegen/releases/tag/v0.1.0
95
+ [0.1.0]: https://github.com/xlameiro/env-typegen/releases/tag/v0.1.0
package/README.md CHANGED
@@ -2,8 +2,8 @@
2
2
 
3
3
  > From `.env.example` to TypeScript in one command.
4
4
 
5
- [![npm version](https://badge.fury.io/js/env-typegen.svg)](https://npmjs.com/package/env-typegen)
6
- [![CI](https://github.com/YOUR_USERNAME/env-typegen/actions/workflows/ci.yml/badge.svg)](https://github.com/YOUR_USERNAME/env-typegen/actions/workflows/ci.yml)
5
+ [![npm version](https://badge.fury.io/js/%40xlameiro%2Fenv-typegen.svg)](https://npmjs.com/package/@xlameiro/env-typegen)
6
+ [![CI](https://github.com/xlameiro/env-typegen/actions/workflows/ci.yml/badge.svg)](https://github.com/xlameiro/env-typegen/actions/workflows/ci.yml)
7
7
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
8
8
  [![TypeScript](https://img.shields.io/badge/TypeScript-5.x-blue)](https://www.typescriptlang.org/)
9
9
 
@@ -11,31 +11,53 @@
11
11
 
12
12
  `env-typegen` reads `.env.example` files and automatically generates:
13
13
 
14
- - TypeScript types (`type Env = { PORT: number; DATABASE_URL: string }`)
14
+ - TypeScript types (`type EnvVars = { PORT: number; DATABASE_URL: string }`)
15
15
  - Zod v4 schemas (`z.object({ PORT: z.coerce.number() })`)
16
16
  - `@t3-oss/env-nextjs` `createEnv` configuration
17
17
  - `.d.ts` declaration files that augment `NodeJS.ProcessEnv`
18
18
 
19
+ ## Install
20
+
21
+ ```bash
22
+ pnpm add -D @xlameiro/env-typegen
23
+ # or
24
+ npm install --save-dev @xlameiro/env-typegen
25
+ ```
26
+
19
27
  ## Quick Start
20
28
 
21
29
  ```bash
22
- # Generate TypeScript types (default)
30
+ # Generate all outputs (default: typescript + zod + t3 + declaration)
23
31
  npx env-typegen --input .env.example --output src/env.generated.ts
24
32
 
25
- # Generate a Zod schema
33
+ # Generate only a Zod schema
26
34
  npx env-typegen -i .env.example -o src/env.schema.ts -g zod
27
35
 
28
- # Generate multiple outputs and format with Prettier
29
- npx env-typegen -i .env.example -o src/env.ts -g typescript -g zod --format
36
+ # Generate multiple outputs explicitly
37
+ npx env-typegen -i .env.example -o src/env.ts -f typescript -f zod
38
+
39
+ # Generate without running Prettier
40
+ npx env-typegen -i .env.example -o src/env.ts --no-format
30
41
 
31
42
  # Watch mode — regenerate on every change
32
43
  npx env-typegen -i .env.example -o src/env.ts --watch
33
44
  ```
34
45
 
46
+ ## Generator formats
47
+
48
+ Use `-f` / `--format` (or `-g` / `--generator` alias):
49
+
50
+ | Value | Meaning |
51
+ | -------------------- | ------------------------------------ |
52
+ | `ts` or `typescript` | Generate TypeScript types |
53
+ | `zod` | Generate Zod schema |
54
+ | `t3` | Generate `@t3-oss/env-nextjs` config |
55
+ | `declaration` | Generate `.d.ts` declaration |
56
+
35
57
  ## Programmatic API
36
58
 
37
59
  ```ts
38
- import { runGenerate, parseEnvFile, generateTypeScriptTypes } from "env-typegen";
60
+ import { runGenerate, parseEnvFile, generateTypeScriptTypes } from "@xlameiro/env-typegen";
39
61
 
40
62
  // High-level: full pipeline
41
63
  await runGenerate({
@@ -46,7 +68,7 @@ await runGenerate({
46
68
  });
47
69
 
48
70
  // Low-level: parse then generate individually
49
- const parsed = await parseEnvFile(".env.example");
71
+ const parsed = parseEnvFile(".env.example");
50
72
  const ts = generateTypeScriptTypes(parsed);
51
73
  ```
52
74
 
@@ -55,7 +77,7 @@ const ts = generateTypeScriptTypes(parsed);
55
77
  Create `env-typegen.config.ts` at your project root:
56
78
 
57
79
  ```ts
58
- import { defineConfig } from "env-typegen";
80
+ import { defineConfig } from "@xlameiro/env-typegen";
59
81
 
60
82
  export default defineConfig({
61
83
  input: ".env.example",
@@ -67,16 +89,11 @@ export default defineConfig({
67
89
 
68
90
  ## Status
69
91
 
70
- | Phase | Description | Status |
71
- | ----- | -------------- | ------ |
72
- | 1 | Foundation | ✅ |
73
- | 2 | Parser | ✅ |
74
- | 3 | Inferrer | ✅ |
75
- | 4 | Generators | ✅ |
76
- | 5 | Config & Utils | ✅ |
77
- | 6 | CLI | ✅ |
78
- | 7 | Quality & Docs | ✅ |
79
- | 8 | Publishing | 🔜 |
92
+ `env-typegen` is actively maintained and published on npm.
93
+
94
+ ## Changelog
95
+
96
+ See [`CHANGELOG.md`](./CHANGELOG.md) for release notes.
80
97
 
81
98
  ## License
82
99
 
package/dist/cli.js CHANGED
@@ -99,8 +99,9 @@ var require_picocolors = __commonJS({
99
99
  });
100
100
 
101
101
  // src/cli.ts
102
+ import { createRequire } from "module";
102
103
  import { realpathSync } from "fs";
103
- import path6 from "path";
104
+ import path7 from "path";
104
105
  import { fileURLToPath, pathToFileURL as pathToFileURL2 } from "url";
105
106
  import { inspect, parseArgs } from "util";
106
107
 
@@ -180,7 +181,7 @@ function generateT3Env(parsed) {
180
181
  const effectiveType = variable.annotatedType ?? variable.inferredType;
181
182
  let zodExpr = toT3ZodType(effectiveType);
182
183
  if (variable.description !== void 0) {
183
- zodExpr += `.describe("${variable.description}")`;
184
+ zodExpr += `.describe("${variable.description.replace(/"/g, '\\"')}")`;
184
185
  }
185
186
  if (variable.isOptional) {
186
187
  zodExpr += ".optional()";
@@ -195,7 +196,7 @@ function generateT3Env(parsed) {
195
196
  const effectiveType = variable.annotatedType ?? variable.inferredType;
196
197
  let zodExpr = toT3ZodType(effectiveType);
197
198
  if (variable.description !== void 0) {
198
- zodExpr += `.describe("${variable.description}")`;
199
+ zodExpr += `.describe("${variable.description.replace(/"/g, '\\"')}")`;
199
200
  }
200
201
  if (variable.isOptional) {
201
202
  zodExpr += ".optional()";
@@ -397,7 +398,9 @@ var inferenceRules = [
397
398
  // src/inferrer/type-inferrer.ts
398
399
  var sortedRules = [...inferenceRules].sort((left, right) => left.priority - right.priority);
399
400
  function inferType(key, value, options) {
400
- for (const rule of sortedRules) {
401
+ const extra = options?.extraRules;
402
+ const rules = extra && extra.length > 0 ? [...extra].sort((a, b) => a.priority - b.priority).concat(sortedRules) : sortedRules;
403
+ for (const rule of rules) {
401
404
  if (rule.match(key, value)) {
402
405
  return rule.type;
403
406
  }
@@ -448,7 +451,7 @@ function parseCommentBlock(lines) {
448
451
  // src/parser/env-parser.ts
449
452
  var ENV_VAR_RE = /^([A-Z_][A-Z0-9_]*)=(.*)$/;
450
453
  var SECTION_HEADER_RE = /^#\s+[-=]{3,}\s+(.+?)\s+[-=]{3,}\s*$/;
451
- function parseEnvFileContent(content, filePath) {
454
+ function parseEnvFileContent(content, filePath, options) {
452
455
  const lines = content.split("\n");
453
456
  const vars = [];
454
457
  const groups = [];
@@ -481,7 +484,11 @@ function parseEnvFileContent(content, filePath) {
481
484
  const key = envMatch[1] ?? "";
482
485
  const rawValue = envMatch[2] ?? "";
483
486
  const annotations = parseCommentBlock(commentBlock);
484
- const inferredType = inferType(key, rawValue);
487
+ const inferredType = inferType(
488
+ key,
489
+ rawValue,
490
+ ...options?.inferenceRules !== void 0 ? [{ extraRules: options.inferenceRules }] : []
491
+ );
485
492
  const isRequired = rawValue.length > 0 || annotations.isRequired;
486
493
  const isOptional = rawValue.length === 0 && !annotations.isRequired;
487
494
  const isClientSide = key.startsWith("NEXT_PUBLIC_");
@@ -529,7 +536,11 @@ import { format } from "prettier";
529
536
  async function formatOutput(content, parser = "typescript") {
530
537
  try {
531
538
  return await format(content, { parser });
532
- } catch {
539
+ } catch (err) {
540
+ console.warn(
541
+ "env-typegen: Prettier formatting failed, writing unformatted output.",
542
+ err instanceof Error ? err.message : String(err)
543
+ );
533
544
  return content;
534
545
  }
535
546
  }
@@ -555,6 +566,12 @@ function deriveOutputPath(base, generator, isSingle) {
555
566
  const outExt = generator === "declaration" ? ".d.ts" : baseExt;
556
567
  return `${noExt}.${generator}${outExt}`;
557
568
  }
569
+ function deriveOutputBaseForInput(output, inputPath) {
570
+ const dir = path5.dirname(output);
571
+ const ext = path5.extname(output);
572
+ const stem = path5.basename(inputPath, path5.extname(inputPath));
573
+ return path5.join(dir, `${stem}${ext}`);
574
+ }
558
575
  function buildOutput(generator, parsed) {
559
576
  switch (generator) {
560
577
  case "typescript":
@@ -580,7 +597,11 @@ async function persistOutput(params) {
580
597
  }
581
598
  if (dryRun) {
582
599
  if (!silent) {
583
- success(`Dry run: ${outputPath}`);
600
+ if (!isSingle) {
601
+ console.log(`// --- ${generator}: ${outputPath} ---`);
602
+ }
603
+ console.log(generated);
604
+ success(`Dry run: would write ${outputPath}`);
584
605
  }
585
606
  return;
586
607
  }
@@ -597,47 +618,97 @@ async function runGenerate(options) {
597
618
  format: shouldFormat,
598
619
  stdout = false,
599
620
  dryRun = false,
600
- silent = false
621
+ silent = false,
622
+ inferenceRules: inferenceRules2
601
623
  } = options;
624
+ const inputs = Array.isArray(input) ? input : [input];
625
+ const hasMultipleInputs = inputs.length > 1;
602
626
  const isSingle = generators.length === 1;
603
- const content = await readEnvFile(input);
604
- const parsed = parseEnvFileContent(content, input);
605
- for (const generator of generators) {
606
- let generated = buildOutput(generator, parsed);
607
- if (shouldFormat) {
608
- generated = await formatOutput(generated);
627
+ for (const inputPath of inputs) {
628
+ const outputBase = hasMultipleInputs ? deriveOutputBaseForInput(output, inputPath) : output;
629
+ const content = await readEnvFile(inputPath);
630
+ const parsed = parseEnvFileContent(
631
+ content,
632
+ inputPath,
633
+ inferenceRules2 !== void 0 ? { inferenceRules: inferenceRules2 } : void 0
634
+ );
635
+ for (const generator of generators) {
636
+ let generated = buildOutput(generator, parsed);
637
+ if (shouldFormat && !dryRun) {
638
+ generated = await formatOutput(generated);
639
+ }
640
+ const outputPath = deriveOutputPath(outputBase, generator, isSingle);
641
+ await persistOutput({
642
+ generated,
643
+ generator,
644
+ outputPath,
645
+ isSingle,
646
+ stdout,
647
+ dryRun,
648
+ silent
649
+ });
609
650
  }
610
- const outputPath = deriveOutputPath(output, generator, isSingle);
611
- await persistOutput({
612
- generated,
613
- generator,
614
- outputPath,
615
- isSingle,
616
- stdout,
617
- dryRun,
618
- silent
619
- });
620
651
  }
621
652
  }
622
653
 
623
654
  // src/watch.ts
655
+ import path6 from "path";
624
656
  import { watch } from "chokidar";
625
- function startWatch({ inputPath, runOptions }) {
626
- log(`Watching ${inputPath} for changes...`);
657
+ function debounce(fn, delay) {
658
+ let timer;
659
+ return (...args) => {
660
+ if (timer !== void 0) clearTimeout(timer);
661
+ timer = setTimeout(() => {
662
+ timer = void 0;
663
+ fn(...args);
664
+ }, delay);
665
+ };
666
+ }
667
+ function startWatch({ inputPath, runOptions, cwd = process.cwd() }) {
668
+ const inputLabel = Array.isArray(inputPath) ? inputPath.join(", ") : inputPath;
669
+ log(`Watching ${inputLabel} for changes...`);
627
670
  void runGenerate(runOptions).catch((err) => {
628
671
  const message = err instanceof Error ? err.message : JSON.stringify(err);
629
672
  error(message);
630
673
  });
631
- const watcher = watch(inputPath, { persistent: true });
632
- watcher.on("change", () => {
633
- log(`${inputPath} changed, regenerating...`);
674
+ const handleChange = debounce((eventPath) => {
675
+ log(`${eventPath} changed, regenerating...`);
634
676
  void runGenerate(runOptions).catch((err) => {
635
677
  const message = err instanceof Error ? err.message : JSON.stringify(err);
636
678
  error(message);
637
679
  });
638
- });
680
+ }, 200);
681
+ const handleConfigChange = debounce(async (eventPath) => {
682
+ log(`Config file ${eventPath} changed, reloading...`);
683
+ try {
684
+ const reloaded = await loadConfig(cwd);
685
+ if (reloaded) {
686
+ runOptions.generators = reloaded.generators ?? runOptions.generators;
687
+ runOptions.format = reloaded.format ?? runOptions.format;
688
+ if (reloaded.inferenceRules !== void 0) {
689
+ runOptions.inferenceRules = reloaded.inferenceRules;
690
+ }
691
+ }
692
+ void runGenerate(runOptions).catch((err) => {
693
+ const message = err instanceof Error ? err.message : JSON.stringify(err);
694
+ error(message);
695
+ });
696
+ } catch (err) {
697
+ const message = err instanceof Error ? err.message : JSON.stringify(err);
698
+ error(`Failed to reload config: ${message}`);
699
+ }
700
+ }, 200);
701
+ const inputWatcher = watch(inputPath, { persistent: true });
702
+ for (const event of ["add", "change", "unlink"]) {
703
+ inputWatcher.on(event, handleChange);
704
+ }
705
+ const configPaths = CONFIG_FILE_NAMES.map((name) => path6.resolve(cwd, name));
706
+ const configWatcher = watch(configPaths, { persistent: true, ignoreInitial: true });
707
+ for (const event of ["add", "change"]) {
708
+ configWatcher.on(event, (eventPath) => void handleConfigChange(eventPath));
709
+ }
639
710
  process.on("SIGINT", () => {
640
- void watcher.close().then(() => {
711
+ void Promise.all([inputWatcher.close(), configWatcher.close()]).then(() => {
641
712
  log("Watcher stopped.");
642
713
  process.exit(0);
643
714
  });
@@ -645,7 +716,8 @@ function startWatch({ inputPath, runOptions }) {
645
716
  }
646
717
 
647
718
  // src/cli.ts
648
- var VERSION = "0.1.0";
719
+ var _require = createRequire(import.meta.url);
720
+ var VERSION = _require("../package.json").version;
649
721
  var HELP_TEXT = [
650
722
  "env-typegen \u2014 Generate TypeScript types from .env.example",
651
723
  "",
@@ -653,7 +725,7 @@ var HELP_TEXT = [
653
725
  " env-typegen -i <path> [options]",
654
726
  "",
655
727
  "Options:",
656
- " -i, --input <path> Path to .env.example file",
728
+ " -i, --input <path> Path to .env.example file(s). May be specified multiple times.",
657
729
  " -o, --output <path> Output file path (default: env.generated.ts)",
658
730
  " -f, --format <name> Generator format: ts|zod|t3|declaration",
659
731
  " May be specified multiple times.",
@@ -684,11 +756,16 @@ function getErrorMessage(errorValue) {
684
756
  return inspect(errorValue, { depth: 2 });
685
757
  }
686
758
  function resolveConfigRelative(value, configDir) {
687
- if (value === void 0 || path6.isAbsolute(value)) return value;
688
- return path6.resolve(configDir, value);
759
+ if (value === void 0 || path7.isAbsolute(value)) return value;
760
+ return path7.resolve(configDir, value);
689
761
  }
690
762
  function applyConfigPaths(config, configDir) {
691
- const input = resolveConfigRelative(config.input, configDir);
763
+ let input;
764
+ if (Array.isArray(config.input)) {
765
+ input = config.input.map((v) => resolveConfigRelative(v, configDir) ?? v);
766
+ } else {
767
+ input = resolveConfigRelative(config.input, configDir);
768
+ }
692
769
  const output = resolveConfigRelative(config.output, configDir);
693
770
  return {
694
771
  ...config,
@@ -700,7 +777,7 @@ async function runCli(argv = process.argv.slice(2)) {
700
777
  const { values } = parseArgs({
701
778
  args: argv,
702
779
  options: {
703
- input: { type: "string", short: "i" },
780
+ input: { type: "string", short: "i", multiple: true },
704
781
  output: { type: "string", short: "o" },
705
782
  generator: { type: "string", short: "g", multiple: true },
706
783
  format: { type: "string", short: "f", multiple: true },
@@ -726,15 +803,16 @@ async function runCli(argv = process.argv.slice(2)) {
726
803
  if (values.config === void 0) {
727
804
  fileConfig = await loadConfig(process.cwd());
728
805
  } else {
729
- const configPath = path6.resolve(values.config);
730
- const configDir = path6.dirname(configPath);
806
+ const configPath = path7.resolve(values.config);
807
+ const configDir = path7.dirname(configPath);
731
808
  const mod = await import(pathToFileURL2(configPath).href);
732
809
  const rawConfig = mod.default;
733
810
  if (rawConfig) {
734
811
  fileConfig = applyConfigPaths(rawConfig, configDir);
735
812
  }
736
813
  }
737
- const input = values.input ?? fileConfig?.input;
814
+ const cliInput = values.input?.length ? values.input : void 0;
815
+ const input = cliInput ?? fileConfig?.input;
738
816
  if (input === void 0) {
739
817
  error("No input file specified. Use -i <path> or set input in env-typegen.config.ts");
740
818
  process.exit(1);
@@ -753,7 +831,7 @@ async function runCli(argv = process.argv.slice(2)) {
753
831
  }
754
832
  generators = [...new Set(normalizedGenerators)];
755
833
  } else {
756
- generators = fileConfig?.generators ?? ["typescript"];
834
+ generators = fileConfig?.generators ?? ["typescript", "zod", "t3", "declaration"];
757
835
  }
758
836
  const shouldFormat = values["no-format"] === true ? false : fileConfig?.format ?? true;
759
837
  const useStdout = values.stdout ?? false;
@@ -767,7 +845,8 @@ async function runCli(argv = process.argv.slice(2)) {
767
845
  format: shouldFormat,
768
846
  stdout: useStdout,
769
847
  dryRun: isDryRun,
770
- silent: isSilent
848
+ silent: isSilent,
849
+ ...fileConfig?.inferenceRules !== void 0 && { inferenceRules: fileConfig.inferenceRules }
771
850
  };
772
851
  if (shouldWatch) {
773
852
  startWatch({ inputPath: input, runOptions: options });
@@ -777,7 +856,7 @@ async function runCli(argv = process.argv.slice(2)) {
777
856
  }
778
857
  if (process.argv[1] !== void 0 && (() => {
779
858
  try {
780
- return realpathSync(path6.resolve(process.argv[1])) === realpathSync(fileURLToPath(import.meta.url));
859
+ return realpathSync(path7.resolve(process.argv[1])) === realpathSync(fileURLToPath(import.meta.url));
781
860
  } catch {
782
861
  return false;
783
862
  }