@flareapp/react-native-sourcemaps 2.10.0 → 2.12.0

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/dist/babel.cjs CHANGED
@@ -1,4 +1,4 @@
1
- const require_version = require('./version-DS96fXWD.cjs');
1
+ const require_version = require('./version-CoiIhPxt.cjs');
2
2
 
3
3
  //#region src/babel.ts
4
4
  const RUNTIME_SOURCE = "@flareapp/react-native-sourcemaps/runtime";
@@ -39,7 +39,6 @@ function flareSourcemapsBabelPlugin({ types: t }) {
39
39
  } }
40
40
  };
41
41
  }
42
- /** Matches `import { flareSourcemapVersion } from '.../runtime'` (named, possibly aliased). */
43
42
  function isFlareVersionSpecifier(specifier, t) {
44
43
  if (!t.isImportSpecifier(specifier)) return false;
45
44
  const imported = specifier.imported;
package/dist/babel.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import { n as resolveVersion } from "./version-DizeDGRU.mjs";
1
+ import { n as resolveVersion } from "./version-BvzhWgI1.mjs";
2
2
 
3
3
  //#region src/babel.ts
4
4
  const RUNTIME_SOURCE = "@flareapp/react-native-sourcemaps/runtime";
@@ -39,7 +39,6 @@ function flareSourcemapsBabelPlugin({ types: t }) {
39
39
  } }
40
40
  };
41
41
  }
42
- /** Matches `import { flareSourcemapVersion } from '.../runtime'` (named, possibly aliased). */
43
42
  function isFlareVersionSpecifier(specifier, t) {
44
43
  if (!t.isImportSpecifier(specifier)) return false;
45
44
  const imported = specifier.imported;
package/dist/bin.cjs CHANGED
@@ -1,18 +1,15 @@
1
1
  #!/usr/bin/env node
2
- const require_uploadSourcemaps = require('./uploadSourcemaps-DmFyPNoM.cjs');
3
- const require_version = require('./version-DS96fXWD.cjs');
2
+ const require_uploadSourcemaps = require('./uploadSourcemaps-BV__K2fF.cjs');
3
+ const require_version = require('./version-CoiIhPxt.cjs');
4
4
  let node_fs = require("node:fs");
5
5
  let node_path = require("node:path");
6
6
 
7
7
  //#region src/banner.ts
8
8
  const BORDER = "=".repeat(60);
9
- /** CI commonly archives build logs. A long key keeps a head/tail hint so it stays recognisable; a short
10
- * one is masked completely. Never returns the key in full. */
11
9
  function maskApiKey(apiKey) {
12
10
  if (apiKey.length <= 12) return "*".repeat(apiKey.length);
13
11
  return `${apiKey.slice(0, 4)}${"*".repeat(8)}${apiKey.slice(-4)}`;
14
12
  }
15
- /** Deliberately large: a one-line "failed to upload" is too easy to miss in a native build log. */
16
13
  function formatFailureBanner(info) {
17
14
  const sourcemap = info.sourcemap ?? "<path-to-map>";
18
15
  const bundleFilename = info.bundleFilename ?? "<bundle-filename>";
@@ -40,12 +37,6 @@ function printFailureBanner(info) {
40
37
 
41
38
  //#endregion
42
39
  //#region src/config.ts
43
- /**
44
- * Read flare.json from an explicit path. Hooks run from android/ or ios/, so resolving relative to
45
- * cwd would read the wrong file; callers always pass --config. Missing or malformed file yields an
46
- * empty config, so resolution falls through to env then to the no-key skip-with-banner. Any `version`
47
- * key is ignored: in the auto path version flows only through FLARE_SOURCEMAP_VERSION.
48
- */
49
40
  function readFlareConfig(configPath) {
50
41
  if (!configPath) return {};
51
42
  try {
@@ -67,16 +58,6 @@ function asString(value) {
67
58
  //#endregion
68
59
  //#region src/env.ts
69
60
  const ENV_FILES = [".env.local", ".env"];
70
- /**
71
- * Load FLARE_* variables into process.env without overwriting anything already set.
72
- *
73
- * Native build hooks run in a fresh process that usually lacks the upload key: Metro loads .env at JS
74
- * runtime, not native build time, and an IDE build or a reused Gradle daemon never sees a key exported in
75
- * your shell. Reading the file makes "drop FLARE_API_KEY in .env.local" work on both platforms.
76
- *
77
- * Only FLARE_-prefixed keys are read, never unrelated secrets. The parser is minimal to avoid a
78
- * dependency; it does not strip inline comments, which is fine for the keys Flare issues.
79
- */
80
61
  function loadEnvFiles(rootDir) {
81
62
  for (const file of ENV_FILES) {
82
63
  let raw;
@@ -108,7 +89,6 @@ function parseFlareEnv(raw) {
108
89
  //#endregion
109
90
  //#region src/cli.ts
110
91
  const USAGE = "Usage: flare-rn-sourcemaps upload --sourcemap <path> [--api-key <key>] [--bundle-filename <name>] [--version <v>] [--api-endpoint <url>] [--config <flare.json>] [--auto]";
111
- /** Minimal `--flag value` / `--flag` parser. No external dependency. */
112
92
  function parseArgs(argv) {
113
93
  const [command, ...rest] = argv;
114
94
  const flags = {};
@@ -169,12 +149,6 @@ async function runCli(argv) {
169
149
  apiEndpoint
170
150
  });
171
151
  }
172
- /**
173
- * Build-hook upload path. Never throws and never sets a non-zero exit code (a Gradle doLast / Xcode
174
- * run-script phase aborts the build on a non-zero child). Every failure (no key, unresolved version,
175
- * upload error) prints the banner and returns, leaving the build green. Only arg misuse (handled in
176
- * runCli before here) exits non-zero.
177
- */
178
152
  async function runAutoUpload(options) {
179
153
  const { apiKey, sourcemap, bundleFilename, apiEndpoint } = options;
180
154
  if (!apiKey) {
package/dist/bin.mjs CHANGED
@@ -1,18 +1,15 @@
1
1
  #!/usr/bin/env node
2
- import { t as uploadSourcemaps } from "./uploadSourcemaps-ChmgzzNm.mjs";
3
- import { r as LOG_PREFIX, t as resolveAutoVersion } from "./version-DizeDGRU.mjs";
2
+ import { t as uploadSourcemaps } from "./uploadSourcemaps-kKVzBO2S.mjs";
3
+ import { r as LOG_PREFIX, t as resolveAutoVersion } from "./version-BvzhWgI1.mjs";
4
4
  import { readFileSync } from "node:fs";
5
5
  import { dirname, join } from "node:path";
6
6
 
7
7
  //#region src/banner.ts
8
8
  const BORDER = "=".repeat(60);
9
- /** CI commonly archives build logs. A long key keeps a head/tail hint so it stays recognisable; a short
10
- * one is masked completely. Never returns the key in full. */
11
9
  function maskApiKey(apiKey) {
12
10
  if (apiKey.length <= 12) return "*".repeat(apiKey.length);
13
11
  return `${apiKey.slice(0, 4)}${"*".repeat(8)}${apiKey.slice(-4)}`;
14
12
  }
15
- /** Deliberately large: a one-line "failed to upload" is too easy to miss in a native build log. */
16
13
  function formatFailureBanner(info) {
17
14
  const sourcemap = info.sourcemap ?? "<path-to-map>";
18
15
  const bundleFilename = info.bundleFilename ?? "<bundle-filename>";
@@ -40,12 +37,6 @@ function printFailureBanner(info) {
40
37
 
41
38
  //#endregion
42
39
  //#region src/config.ts
43
- /**
44
- * Read flare.json from an explicit path. Hooks run from android/ or ios/, so resolving relative to
45
- * cwd would read the wrong file; callers always pass --config. Missing or malformed file yields an
46
- * empty config, so resolution falls through to env then to the no-key skip-with-banner. Any `version`
47
- * key is ignored: in the auto path version flows only through FLARE_SOURCEMAP_VERSION.
48
- */
49
40
  function readFlareConfig(configPath) {
50
41
  if (!configPath) return {};
51
42
  try {
@@ -67,16 +58,6 @@ function asString(value) {
67
58
  //#endregion
68
59
  //#region src/env.ts
69
60
  const ENV_FILES = [".env.local", ".env"];
70
- /**
71
- * Load FLARE_* variables into process.env without overwriting anything already set.
72
- *
73
- * Native build hooks run in a fresh process that usually lacks the upload key: Metro loads .env at JS
74
- * runtime, not native build time, and an IDE build or a reused Gradle daemon never sees a key exported in
75
- * your shell. Reading the file makes "drop FLARE_API_KEY in .env.local" work on both platforms.
76
- *
77
- * Only FLARE_-prefixed keys are read, never unrelated secrets. The parser is minimal to avoid a
78
- * dependency; it does not strip inline comments, which is fine for the keys Flare issues.
79
- */
80
61
  function loadEnvFiles(rootDir) {
81
62
  for (const file of ENV_FILES) {
82
63
  let raw;
@@ -108,7 +89,6 @@ function parseFlareEnv(raw) {
108
89
  //#endregion
109
90
  //#region src/cli.ts
110
91
  const USAGE = "Usage: flare-rn-sourcemaps upload --sourcemap <path> [--api-key <key>] [--bundle-filename <name>] [--version <v>] [--api-endpoint <url>] [--config <flare.json>] [--auto]";
111
- /** Minimal `--flag value` / `--flag` parser. No external dependency. */
112
92
  function parseArgs(argv) {
113
93
  const [command, ...rest] = argv;
114
94
  const flags = {};
@@ -169,12 +149,6 @@ async function runCli(argv) {
169
149
  apiEndpoint
170
150
  });
171
151
  }
172
- /**
173
- * Build-hook upload path. Never throws and never sets a non-zero exit code (a Gradle doLast / Xcode
174
- * run-script phase aborts the build on a non-zero child). Every failure (no key, unresolved version,
175
- * upload error) prints the banner and returns, leaving the build green. Only arg misuse (handled in
176
- * runCli before here) exits non-zero.
177
- */
178
152
  async function runAutoUpload(options) {
179
153
  const { apiKey, sourcemap, bundleFilename, apiEndpoint } = options;
180
154
  if (!apiKey) {
package/dist/expo.cjs CHANGED
@@ -10,27 +10,16 @@ let _expo_config_plugins = require("@expo/config-plugins");
10
10
  const FLARE_GRADLE_MARKER = "@flareapp/react-native-sourcemaps Expo config plugin";
11
11
  const SOURCEMAP_FILE_LINE = "export SOURCEMAP_FILE=\"$TARGET_TEMP_DIR/main.jsbundle.map\"";
12
12
  const GITIGNORE_ENTRY = "flare.json";
13
- /** Absent props are omitted so the CLI applies its own defaults and its FLARE_API_KEY fallback. No
14
- * `version` key: that flows only through FLARE_SOURCEMAP_VERSION. */
15
13
  function flareJsonContents(props) {
16
14
  const config = {};
17
15
  if (props.apiKey) config.apiKey = props.apiKey;
18
16
  if (props.apiEndpoint) config.apiEndpoint = props.apiEndpoint;
19
17
  return `${JSON.stringify(config, null, 4)}\n`;
20
18
  }
21
- /**
22
- * Append `apply from: "<path>"` to android/app/build.gradle. Idempotent via a marker comment, so a
23
- * re-prebuild without --clean does not duplicate it.
24
- */
25
19
  function addFlareGradleApply(buildGradle, applyFromPath) {
26
20
  if (buildGradle.includes(FLARE_GRADLE_MARKER)) return buildGradle;
27
21
  return `${buildGradle.endsWith("\n") ? buildGradle : `${buildGradle}\n`}\n// ${FLARE_GRADLE_MARKER}\napply from: ${JSON.stringify(applyFromPath)}\n`;
28
22
  }
29
- /**
30
- * Exports SOURCEMAP_FILE so the stock bundle phase emits the composed map. Idempotent, and treats a
31
- * missing file as valid input. The guard is line-based and ignores comments, so a commented-out
32
- * `# SOURCEMAP_FILE=` does not suppress injection while a real one is left untouched.
33
- */
34
23
  function addSourcemapFileEnv(xcodeEnv) {
35
24
  if (xcodeEnv.split("\n").some((line) => {
36
25
  const trimmed = line.trim();
@@ -38,12 +27,10 @@ function addSourcemapFileEnv(xcodeEnv) {
38
27
  })) return xcodeEnv;
39
28
  return `${xcodeEnv.length === 0 || xcodeEnv.endsWith("\n") ? xcodeEnv : `${xcodeEnv}\n`}# Flare: emit the composed Hermes sourcemap so the upload phase can find it\n${SOURCEMAP_FILE_LINE}\n`;
40
29
  }
41
- /** Append `flare.json` to .gitignore once (it is generated from app.json props). */
42
30
  function ensureGitignored(gitignore, entry = GITIGNORE_ENTRY) {
43
31
  if (gitignore.split("\n").some((line) => line.trim() === entry)) return gitignore;
44
32
  return `${gitignore.length === 0 || gitignore.endsWith("\n") ? gitignore : `${gitignore}\n`}${entry}\n`;
45
33
  }
46
- /** Sources RN's with-environment.sh first, so SOURCEMAP_FILE and FLARE_* are present. */
47
34
  function flareXcodeShellScript(withEnvironmentPath, flareXcodePath) {
48
35
  return [
49
36
  "set -e",
@@ -53,7 +40,6 @@ function flareXcodeShellScript(withEnvironmentPath, flareXcodePath) {
53
40
  ""
54
41
  ].join("\n");
55
42
  }
56
- /** path.relative normalised to forward slashes (Gradle/Xcode paths are posix even on Windows). */
57
43
  function toPosixRelative(fromDir, target) {
58
44
  return (0, node_path.relative)(fromDir, target).split(node_path.sep).join("/");
59
45
  }
@@ -62,10 +48,6 @@ function toPosixRelative(fromDir, target) {
62
48
  //#region src/expoXcode.ts
63
49
  const FLARE_PHASE_NAME = "Upload Flare sourcemaps";
64
50
  const ALWAYS_OUT_OF_DATE = 1;
65
- /**
66
- * Appended rather than positioned, so it runs after the JS bundle phase whatever that phase is named and
67
- * wherever it sits across Expo SDKs. Mutates `project` in place, per the `xcode` lib's contract.
68
- */
69
51
  function addUploadBuildPhase(project, shellScript) {
70
52
  const internal = project;
71
53
  const phases = internal.hash.project.objects.PBXShellScriptBuildPhase ?? {};
@@ -83,7 +65,7 @@ function addUploadBuildPhase(project, shellScript) {
83
65
  var require_package = /* @__PURE__ */ __commonJSMin(((exports, module) => {
84
66
  module.exports = {
85
67
  "name": "@flareapp/react-native-sourcemaps",
86
- "version": "2.10.0",
68
+ "version": "2.12.0",
87
69
  "description": "Upload React Native (Metro) sourcemaps to Flare",
88
70
  "homepage": "https://flareapp.io",
89
71
  "bugs": "https://github.com/spatie/flare-client-js/issues",
package/dist/index.cjs CHANGED
@@ -1,6 +1,6 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
2
- const require_uploadSourcemaps = require('./uploadSourcemaps-DmFyPNoM.cjs');
3
- const require_version = require('./version-DS96fXWD.cjs');
2
+ const require_uploadSourcemaps = require('./uploadSourcemaps-BV__K2fF.cjs');
3
+ const require_version = require('./version-CoiIhPxt.cjs');
4
4
  const require_babel = require('./babel.cjs');
5
5
 
6
6
  exports.flareSourcemapsBabelPlugin = require_babel;
package/dist/index.d.cts CHANGED
@@ -4,10 +4,8 @@ import flareSourcemapsBabelPlugin from "./babel.cjs";
4
4
  type UploadSourcemapsOptions = {
5
5
  /** Flare project API key. */apiKey: string; /** Path to the composed `.map` file to upload. */
6
6
  sourcemap: string;
7
- /**
8
- * The `relative_filename` the backend matches against runtime stack frames.
9
- * Defaults to the map's basename minus `.map` (e.g. `index.android.bundle`).
10
- */
7
+ /** The `relative_filename` the backend matches against runtime stack frames. Defaults to the map's
8
+ * basename minus `.map` (e.g. `index.android.bundle`). */
11
9
  bundleFilename?: string; /** Explicit version; otherwise resolved from env/package.json. */
12
10
  version?: string; /** Override the Flare sourcemaps endpoint. */
13
11
  apiEndpoint?: string;
@@ -20,9 +18,9 @@ type ResolveVersionOptions = {
20
18
  cwd?: string;
21
19
  };
22
20
  /**
23
- * Resolve the sourcemap version shared by the Babel plugin and the CLI. Precedence: explicit
24
- * `version` > `FLARE_SOURCEMAP_VERSION` env > package.json `version` (with a warning). Never random: a
25
- * random default would silently put the inlined runtime value out of sync with the uploaded `version_id`.
21
+ * Resolve the sourcemap version shared by the Babel plugin and the CLI. Precedence: explicit `version` >
22
+ * `FLARE_SOURCEMAP_VERSION` env > package.json `version` (with a warning). Never random a random default
23
+ * would silently put the inlined runtime value out of sync with the uploaded `version_id`.
26
24
  */
27
25
  declare function resolveVersion({
28
26
  version,
package/dist/index.d.mts CHANGED
@@ -4,10 +4,8 @@ import flareSourcemapsBabelPlugin from "./babel.mjs";
4
4
  type UploadSourcemapsOptions = {
5
5
  /** Flare project API key. */apiKey: string; /** Path to the composed `.map` file to upload. */
6
6
  sourcemap: string;
7
- /**
8
- * The `relative_filename` the backend matches against runtime stack frames.
9
- * Defaults to the map's basename minus `.map` (e.g. `index.android.bundle`).
10
- */
7
+ /** The `relative_filename` the backend matches against runtime stack frames. Defaults to the map's
8
+ * basename minus `.map` (e.g. `index.android.bundle`). */
11
9
  bundleFilename?: string; /** Explicit version; otherwise resolved from env/package.json. */
12
10
  version?: string; /** Override the Flare sourcemaps endpoint. */
13
11
  apiEndpoint?: string;
@@ -20,9 +18,9 @@ type ResolveVersionOptions = {
20
18
  cwd?: string;
21
19
  };
22
20
  /**
23
- * Resolve the sourcemap version shared by the Babel plugin and the CLI. Precedence: explicit
24
- * `version` > `FLARE_SOURCEMAP_VERSION` env > package.json `version` (with a warning). Never random: a
25
- * random default would silently put the inlined runtime value out of sync with the uploaded `version_id`.
21
+ * Resolve the sourcemap version shared by the Babel plugin and the CLI. Precedence: explicit `version` >
22
+ * `FLARE_SOURCEMAP_VERSION` env > package.json `version` (with a warning). Never random a random default
23
+ * would silently put the inlined runtime value out of sync with the uploaded `version_id`.
26
24
  */
27
25
  declare function resolveVersion({
28
26
  version,
package/dist/index.mjs CHANGED
@@ -1,5 +1,5 @@
1
- import { t as uploadSourcemaps } from "./uploadSourcemaps-ChmgzzNm.mjs";
2
- import { n as resolveVersion } from "./version-DizeDGRU.mjs";
1
+ import { t as uploadSourcemaps } from "./uploadSourcemaps-kKVzBO2S.mjs";
2
+ import { n as resolveVersion } from "./version-BvzhWgI1.mjs";
3
3
  import flareSourcemapsBabelPlugin from "./babel.mjs";
4
4
 
5
5
  export { flareSourcemapsBabelPlugin, resolveVersion, uploadSourcemaps };
@@ -1,4 +1,4 @@
1
- const require_version = require('./version-DS96fXWD.cjs');
1
+ const require_version = require('./version-CoiIhPxt.cjs');
2
2
  let node_fs = require("node:fs");
3
3
  let node_path = require("node:path");
4
4
  let node_zlib = require("node:zlib");
@@ -1,4 +1,4 @@
1
- import { n as resolveVersion, r as LOG_PREFIX } from "./version-DizeDGRU.mjs";
1
+ import { n as resolveVersion, r as LOG_PREFIX } from "./version-BvzhWgI1.mjs";
2
2
  import { readFileSync } from "node:fs";
3
3
  import { basename } from "node:path";
4
4
  import { deflateRawSync } from "node:zlib";
@@ -7,9 +7,9 @@ const LOG_PREFIX = "@flareapp/react-native-sourcemaps";
7
7
  //#endregion
8
8
  //#region src/version.ts
9
9
  /**
10
- * Resolve the sourcemap version shared by the Babel plugin and the CLI. Precedence: explicit
11
- * `version` > `FLARE_SOURCEMAP_VERSION` env > package.json `version` (with a warning). Never random: a
12
- * random default would silently put the inlined runtime value out of sync with the uploaded `version_id`.
10
+ * Resolve the sourcemap version shared by the Babel plugin and the CLI. Precedence: explicit `version` >
11
+ * `FLARE_SOURCEMAP_VERSION` env > package.json `version` (with a warning). Never random a random default
12
+ * would silently put the inlined runtime value out of sync with the uploaded `version_id`.
13
13
  */
14
14
  function resolveVersion({ version, cwd = process.cwd() } = {}) {
15
15
  if (version) return version;
@@ -22,14 +22,6 @@ function resolveVersion({ version, cwd = process.cwd() } = {}) {
22
22
  }
23
23
  throw new Error(`${LOG_PREFIX}: Could not resolve a sourcemap version. Pass --version, set FLARE_SOURCEMAP_VERSION, or ensure package.json has a "version".`);
24
24
  }
25
- /**
26
- * Version resolution for the automatic native upload path. Unlike resolveVersion, never falls back to
27
- * package.json: the hook runs in android/ or ios/ (a different cwd than Metro), so a package.json
28
- * fallback would read a different or missing file and end up out of sync with the version the Babel
29
- * plugin inlined. FLARE_SOURCEMAP_VERSION is the only input both halves are guaranteed to see the same
30
- * way. Returns null
31
- * when unresolved so the caller can skip-with-banner rather than upload a mismatched map.
32
- */
33
25
  function resolveAutoVersion(version) {
34
26
  if (version) return version;
35
27
  const envVersion = process.env.FLARE_SOURCEMAP_VERSION;
@@ -7,9 +7,9 @@ const LOG_PREFIX = "@flareapp/react-native-sourcemaps";
7
7
  //#endregion
8
8
  //#region src/version.ts
9
9
  /**
10
- * Resolve the sourcemap version shared by the Babel plugin and the CLI. Precedence: explicit
11
- * `version` > `FLARE_SOURCEMAP_VERSION` env > package.json `version` (with a warning). Never random: a
12
- * random default would silently put the inlined runtime value out of sync with the uploaded `version_id`.
10
+ * Resolve the sourcemap version shared by the Babel plugin and the CLI. Precedence: explicit `version` >
11
+ * `FLARE_SOURCEMAP_VERSION` env > package.json `version` (with a warning). Never random a random default
12
+ * would silently put the inlined runtime value out of sync with the uploaded `version_id`.
13
13
  */
14
14
  function resolveVersion({ version, cwd = process.cwd() } = {}) {
15
15
  if (version) return version;
@@ -22,14 +22,6 @@ function resolveVersion({ version, cwd = process.cwd() } = {}) {
22
22
  }
23
23
  throw new Error(`${LOG_PREFIX}: Could not resolve a sourcemap version. Pass --version, set FLARE_SOURCEMAP_VERSION, or ensure package.json has a "version".`);
24
24
  }
25
- /**
26
- * Version resolution for the automatic native upload path. Unlike resolveVersion, never falls back to
27
- * package.json: the hook runs in android/ or ios/ (a different cwd than Metro), so a package.json
28
- * fallback would read a different or missing file and end up out of sync with the version the Babel
29
- * plugin inlined. FLARE_SOURCEMAP_VERSION is the only input both halves are guaranteed to see the same
30
- * way. Returns null
31
- * when unresolved so the caller can skip-with-banner rather than upload a mismatched map.
32
- */
33
25
  function resolveAutoVersion(version) {
34
26
  if (version) return version;
35
27
  const envVersion = process.env.FLARE_SOURCEMAP_VERSION;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@flareapp/react-native-sourcemaps",
3
- "version": "2.10.0",
3
+ "version": "2.12.0",
4
4
  "description": "Upload React Native (Metro) sourcemaps to Flare",
5
5
  "homepage": "https://flareapp.io",
6
6
  "bugs": "https://github.com/spatie/flare-client-js/issues",