@rn-iso/metro 1.0.0 → 1.3.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/index.d.ts +26 -0
- package/dist/index.js +197 -0
- package/package.json +20 -7
- package/index.js +0 -273
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
//#region index.d.ts
|
|
2
|
+
type FileStoreCtor = new (options: {
|
|
3
|
+
root: string;
|
|
4
|
+
}) => object;
|
|
5
|
+
interface MetroEvent {
|
|
6
|
+
type?: string;
|
|
7
|
+
level?: unknown;
|
|
8
|
+
data?: unknown;
|
|
9
|
+
error?: unknown;
|
|
10
|
+
stack?: string;
|
|
11
|
+
buildID?: string;
|
|
12
|
+
}
|
|
13
|
+
interface NdjsonReporter {
|
|
14
|
+
dir: string;
|
|
15
|
+
update(event: MetroEvent): void;
|
|
16
|
+
readonly drops: number;
|
|
17
|
+
}
|
|
18
|
+
declare function cacheRoot(name?: string | null): string;
|
|
19
|
+
declare function sharedCacheStores(name?: string, { FileStore }?: {
|
|
20
|
+
FileStore?: FileStoreCtor;
|
|
21
|
+
}): object[];
|
|
22
|
+
declare function ndjsonReporter({ dir }?: {
|
|
23
|
+
dir?: string;
|
|
24
|
+
}): NdjsonReporter;
|
|
25
|
+
//#endregion
|
|
26
|
+
export { NdjsonReporter, cacheRoot, ndjsonReporter, sharedCacheStores };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
+
//#region \0rolldown/runtime.js
|
|
3
|
+
var __create = Object.create;
|
|
4
|
+
var __defProp = Object.defineProperty;
|
|
5
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
6
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
7
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
8
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
9
|
+
var __copyProps = (to, from, except, desc) => {
|
|
10
|
+
if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
|
|
11
|
+
key = keys[i];
|
|
12
|
+
if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, {
|
|
13
|
+
get: ((k) => from[k]).bind(null, key),
|
|
14
|
+
enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
return to;
|
|
18
|
+
};
|
|
19
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule || !__hasOwnProp.call(mod, "default") ? __defProp(target, "default", {
|
|
20
|
+
value: mod,
|
|
21
|
+
enumerable: true
|
|
22
|
+
}) : target, mod));
|
|
23
|
+
//#endregion
|
|
24
|
+
let node_fs = require("node:fs");
|
|
25
|
+
node_fs = __toESM(node_fs);
|
|
26
|
+
let node_os = require("node:os");
|
|
27
|
+
node_os = __toESM(node_os);
|
|
28
|
+
let node_path = require("node:path");
|
|
29
|
+
node_path = __toESM(node_path);
|
|
30
|
+
//#region index.ts
|
|
31
|
+
function configDir() {
|
|
32
|
+
return process.env.RN_ISO_HOME || node_path.default.join(node_os.default.homedir(), ".rn-iso");
|
|
33
|
+
}
|
|
34
|
+
function cacheNameSegment(name) {
|
|
35
|
+
return String(name).replace(/[^A-Za-z0-9._-]+/g, "-").replace(/^\.+/, "") || "app";
|
|
36
|
+
}
|
|
37
|
+
function cacheRoot(name) {
|
|
38
|
+
if (process.env.RN_ISO_METRO_CACHE) return process.env.RN_ISO_METRO_CACHE;
|
|
39
|
+
const root = node_path.default.join(configDir(), "metro-cache");
|
|
40
|
+
return name === void 0 || name === null || name === "" ? root : node_path.default.join(root, cacheNameSegment(name));
|
|
41
|
+
}
|
|
42
|
+
function registerCache({ dir, name, prune, note, entriesDepth }) {
|
|
43
|
+
try {
|
|
44
|
+
const home = configDir();
|
|
45
|
+
const file = node_path.default.join(home, "caches.json");
|
|
46
|
+
let manifest = {
|
|
47
|
+
version: 1,
|
|
48
|
+
caches: []
|
|
49
|
+
};
|
|
50
|
+
try {
|
|
51
|
+
const parsed = JSON.parse(node_fs.default.readFileSync(file, "utf-8"));
|
|
52
|
+
if (Array.isArray(parsed?.caches)) manifest = {
|
|
53
|
+
version: 1,
|
|
54
|
+
caches: parsed.caches
|
|
55
|
+
};
|
|
56
|
+
} catch {}
|
|
57
|
+
const others = manifest.caches.filter((c) => c.dir !== dir);
|
|
58
|
+
const record = {
|
|
59
|
+
dir,
|
|
60
|
+
name,
|
|
61
|
+
prune,
|
|
62
|
+
note,
|
|
63
|
+
registeredBy: process.cwd()
|
|
64
|
+
};
|
|
65
|
+
if (entriesDepth) record.entriesDepth = entriesDepth;
|
|
66
|
+
others.push(record);
|
|
67
|
+
node_fs.default.mkdirSync(home, { recursive: true });
|
|
68
|
+
node_fs.default.writeFileSync(file, JSON.stringify({
|
|
69
|
+
version: 1,
|
|
70
|
+
caches: others
|
|
71
|
+
}, null, 2));
|
|
72
|
+
} catch {}
|
|
73
|
+
}
|
|
74
|
+
function registerOnce(dir) {
|
|
75
|
+
registerCache({
|
|
76
|
+
dir,
|
|
77
|
+
name: "Metro transform cache",
|
|
78
|
+
prune: "entries",
|
|
79
|
+
entriesDepth: 2,
|
|
80
|
+
note: "shared Metro transforms; no eviction of its own"
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
function sharedCacheStores(name = "app", { FileStore } = {}) {
|
|
84
|
+
const Store = FileStore || require("metro-cache").FileStore;
|
|
85
|
+
const root = cacheRoot(name);
|
|
86
|
+
registerOnce(root);
|
|
87
|
+
return [new Store({ root })];
|
|
88
|
+
}
|
|
89
|
+
const NDJSON_LEVELS = /* @__PURE__ */ new Set([
|
|
90
|
+
"debug",
|
|
91
|
+
"info",
|
|
92
|
+
"warn",
|
|
93
|
+
"error",
|
|
94
|
+
"fatal"
|
|
95
|
+
]);
|
|
96
|
+
function ndjsonLevel(level, fallback) {
|
|
97
|
+
const value = String(level === void 0 || level === null ? "" : level).toLowerCase();
|
|
98
|
+
if (NDJSON_LEVELS.has(value)) return value;
|
|
99
|
+
switch (value) {
|
|
100
|
+
case "log":
|
|
101
|
+
case "dir":
|
|
102
|
+
case "table":
|
|
103
|
+
case "group":
|
|
104
|
+
case "groupcollapsed":
|
|
105
|
+
case "groupend": return "info";
|
|
106
|
+
case "trace": return "debug";
|
|
107
|
+
case "warning": return "warn";
|
|
108
|
+
default: return fallback;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
function formatValue(value) {
|
|
112
|
+
if (typeof value === "string") return value;
|
|
113
|
+
if (value instanceof Error) return value.message || String(value);
|
|
114
|
+
try {
|
|
115
|
+
const json = JSON.stringify(value);
|
|
116
|
+
if (json !== void 0) return json;
|
|
117
|
+
} catch {}
|
|
118
|
+
try {
|
|
119
|
+
return String(value);
|
|
120
|
+
} catch {
|
|
121
|
+
return "[unprintable]";
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
function formatData(data) {
|
|
125
|
+
if (data === void 0 || data === null) return "";
|
|
126
|
+
if (Array.isArray(data)) return data.map(formatValue).join(" ");
|
|
127
|
+
return formatValue(data);
|
|
128
|
+
}
|
|
129
|
+
function errorMessage(error) {
|
|
130
|
+
if (error === void 0 || error === null) return "unknown error";
|
|
131
|
+
if (typeof error === "string") return error;
|
|
132
|
+
if (typeof error.message === "string" && error.message) return error.message;
|
|
133
|
+
return formatValue(error);
|
|
134
|
+
}
|
|
135
|
+
function ndjsonReporter({ dir } = {}) {
|
|
136
|
+
const logDir = dir || node_path.default.join(process.cwd(), ".rn-iso", "logs");
|
|
137
|
+
let ensured = false;
|
|
138
|
+
let drops = 0;
|
|
139
|
+
function write(file, record) {
|
|
140
|
+
try {
|
|
141
|
+
if (!ensured) {
|
|
142
|
+
node_fs.default.mkdirSync(logDir, { recursive: true });
|
|
143
|
+
ensured = true;
|
|
144
|
+
}
|
|
145
|
+
node_fs.default.appendFileSync(node_path.default.join(logDir, file), JSON.stringify(record) + "\n");
|
|
146
|
+
} catch {
|
|
147
|
+
drops += 1;
|
|
148
|
+
ensured = false;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
function update(event) {
|
|
152
|
+
try {
|
|
153
|
+
const type = event && typeof event.type === "string" ? event.type : "";
|
|
154
|
+
const record = {
|
|
155
|
+
ts: Date.now(),
|
|
156
|
+
src: "metro",
|
|
157
|
+
level: "debug",
|
|
158
|
+
msg: ""
|
|
159
|
+
};
|
|
160
|
+
if (type) record.event = type;
|
|
161
|
+
if (type === "client_log") {
|
|
162
|
+
record.src = "client";
|
|
163
|
+
record.level = ndjsonLevel(event.level, "info");
|
|
164
|
+
record.msg = formatData(event.data);
|
|
165
|
+
if (event.stack) record.stack = event.stack;
|
|
166
|
+
write("client.ndjson", record);
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
169
|
+
if (type === "bundling_error" || type === "transformer_error") {
|
|
170
|
+
record.level = "error";
|
|
171
|
+
record.msg = errorMessage(event.error);
|
|
172
|
+
} else if (type === "bundle_build_done" || type === "bundle_build_failed") {
|
|
173
|
+
const what = type === "bundle_build_done" ? "bundle build done" : "bundle build failed";
|
|
174
|
+
record.level = "info";
|
|
175
|
+
record.msg = event.buildID ? `${what} (${event.buildID})` : what;
|
|
176
|
+
record.marker = true;
|
|
177
|
+
} else if (type === "unstable_server_log") {
|
|
178
|
+
record.level = ndjsonLevel(event.level, "info");
|
|
179
|
+
record.msg = formatData(event.data);
|
|
180
|
+
} else record.msg = formatData(event && event.data) || type || "metro event";
|
|
181
|
+
write("metro.ndjson", record);
|
|
182
|
+
} catch {
|
|
183
|
+
drops += 1;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
return {
|
|
187
|
+
dir: logDir,
|
|
188
|
+
update,
|
|
189
|
+
get drops() {
|
|
190
|
+
return drops;
|
|
191
|
+
}
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
//#endregion
|
|
195
|
+
exports.cacheRoot = cacheRoot;
|
|
196
|
+
exports.ndjsonReporter = ndjsonReporter;
|
|
197
|
+
exports.sharedCacheStores = sharedCacheStores;
|
package/package.json
CHANGED
|
@@ -1,17 +1,30 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rn-iso/metro",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.3.0",
|
|
4
4
|
"description": "Metro integration for rn-iso: one transform cache shared by every worktree, plus an NDJSON reporter for structured logs.",
|
|
5
|
-
"
|
|
5
|
+
"license": "MIT",
|
|
6
6
|
"files": [
|
|
7
|
-
"
|
|
8
|
-
"README.md"
|
|
7
|
+
"dist",
|
|
8
|
+
"README.md",
|
|
9
|
+
"LICENSE"
|
|
9
10
|
],
|
|
10
|
-
"
|
|
11
|
+
"main": "dist/index.js",
|
|
12
|
+
"types": "dist/index.d.ts",
|
|
13
|
+
"exports": {
|
|
14
|
+
".": {
|
|
15
|
+
"types": "./dist/index.d.ts",
|
|
16
|
+
"require": "./dist/index.js",
|
|
17
|
+
"default": "./dist/index.js"
|
|
18
|
+
}
|
|
19
|
+
},
|
|
11
20
|
"scripts": {
|
|
12
|
-
"test": "node --test test/*.test.js"
|
|
21
|
+
"test": "node --test test/*.test.js",
|
|
22
|
+
"build": "tsdown"
|
|
13
23
|
},
|
|
14
24
|
"peerDependencies": {
|
|
15
25
|
"metro-cache": "*"
|
|
26
|
+
},
|
|
27
|
+
"engines": {
|
|
28
|
+
"node": ">=22"
|
|
16
29
|
}
|
|
17
|
-
}
|
|
30
|
+
}
|
package/index.js
DELETED
|
@@ -1,273 +0,0 @@
|
|
|
1
|
-
// The two things rn-iso wires into Metro: a transform cache shared by every
|
|
2
|
-
// worktree on the machine, and a reporter that writes the dev server's events
|
|
3
|
-
// as NDJSON.
|
|
4
|
-
//
|
|
5
|
-
// Metro's default cache lives under the project, so a second worktree starts
|
|
6
|
-
// cold and re-transforms the whole module graph -- thousands of modules, every
|
|
7
|
-
// time. Pointing every checkout at one store means only the first one pays.
|
|
8
|
-
//
|
|
9
|
-
// const { sharedCacheStores } = require('@rn-iso/metro');
|
|
10
|
-
// config.cacheStores = sharedCacheStores('myapp');
|
|
11
|
-
//
|
|
12
|
-
// The thin part is the FileStore. The part worth packaging is telling rn-iso the
|
|
13
|
-
// cache exists, so `gc` can report and trim it -- Metro's FileStore has
|
|
14
|
-
// no eviction logic whatsoever, so without that it grows until the disk does.
|
|
15
|
-
//
|
|
16
|
-
// The reporter is the other half, and it only works when Metro is hosted
|
|
17
|
-
// programmatically: both the Expo CLI and the React Native CLI overwrite
|
|
18
|
-
// config.reporter after loading metro.config.js, so a reporter set there is
|
|
19
|
-
// discarded. rn-iso's supervisor hosts Metro itself and passes this one in.
|
|
20
|
-
//
|
|
21
|
-
// const { ndjsonReporter } = require('@rn-iso/metro');
|
|
22
|
-
// config.reporter = ndjsonReporter({ dir: '<root>/.rn-iso/logs' });
|
|
23
|
-
|
|
24
|
-
const fs = require('node:fs');
|
|
25
|
-
const os = require('node:os');
|
|
26
|
-
const path = require('node:path');
|
|
27
|
-
|
|
28
|
-
// THIS RESOLUTION EXISTS THREE TIMES: here, in
|
|
29
|
-
// packages/expo-build-cache/index.js, and in rn-iso's own src/paths.js
|
|
30
|
-
// (sharedMetroCache / sharedBuildCache). This package cannot import that module
|
|
31
|
-
// -- it has to work on a machine with no rn-iso installed at all -- so the
|
|
32
|
-
// duplication is deliberate, the same way buildCacheKey is duplicated between
|
|
33
|
-
// the build-cache implementations. Change one and you must change all three:
|
|
34
|
-
// when they drift, one entry point writes a cache the other will never read,
|
|
35
|
-
// and neither of them says so. rn-iso's test/cache-packages.test.js asserts all
|
|
36
|
-
// three agree.
|
|
37
|
-
//
|
|
38
|
-
// RN_ISO_METRO_CACHE comes first because it did before the layout existed, and
|
|
39
|
-
// quietly ignoring an override someone already set reads as an empty cache
|
|
40
|
-
// rather than as an error. It names one directory, so it wins for a named cache
|
|
41
|
-
// too -- otherwise half the stores on a machine would move and half would not.
|
|
42
|
-
function configDir() {
|
|
43
|
-
return process.env.RN_ISO_HOME || path.join(os.homedir(), '.rn-iso');
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
// Anything that is not a plain path segment is replaced, and leading dots go, so
|
|
47
|
-
// a scoped package name cannot climb out of the cache root.
|
|
48
|
-
function cacheNameSegment(name) {
|
|
49
|
-
return String(name).replace(/[^A-Za-z0-9._-]+/g, '-').replace(/^\.+/, '') || 'app';
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
function cacheRoot(name) {
|
|
53
|
-
if (process.env.RN_ISO_METRO_CACHE) return process.env.RN_ISO_METRO_CACHE;
|
|
54
|
-
const root = path.join(configDir(), 'metro-cache');
|
|
55
|
-
return name === undefined || name === null || name === '' ? root : path.join(root, cacheNameSegment(name));
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
// Registering makes this cache visible to `rn-iso gc`'s report, which is the
|
|
59
|
-
// only thing that will ever trim it -- Metro's FileStore has no eviction of its
|
|
60
|
-
// own.
|
|
61
|
-
//
|
|
62
|
-
// The manifest is written directly rather than through rn-iso's own module, for
|
|
63
|
-
// two reasons that both made the import silently do nothing:
|
|
64
|
-
// - the documented way to use the CLI is `npx rn-iso`, so it is usually not a
|
|
65
|
-
// dependency of the project and the specifier does not resolve at all
|
|
66
|
-
// - rn-iso is an ES module, so `require` of it throws ERR_REQUIRE_ESM on Node
|
|
67
|
-
// before 20.19
|
|
68
|
-
// A dynamic import fixes the second and not the first.
|
|
69
|
-
function registerCache({ dir, name, prune, note, entriesDepth }) {
|
|
70
|
-
try {
|
|
71
|
-
const home = configDir();
|
|
72
|
-
const file = path.join(home, 'caches.json');
|
|
73
|
-
let manifest = { version: 1, caches: [] };
|
|
74
|
-
try {
|
|
75
|
-
const parsed = JSON.parse(fs.readFileSync(file, 'utf-8'));
|
|
76
|
-
if (Array.isArray(parsed?.caches)) manifest = { version: 1, caches: parsed.caches };
|
|
77
|
-
} catch {
|
|
78
|
-
// No manifest yet, or an unreadable one: start clean rather than fail.
|
|
79
|
-
}
|
|
80
|
-
// Keyed on the directory so repeated calls update rather than accumulate --
|
|
81
|
-
// these run on every build.
|
|
82
|
-
const others = manifest.caches.filter(c => c.dir !== dir);
|
|
83
|
-
const record = { dir, name, prune, note, registeredBy: process.cwd() };
|
|
84
|
-
// Only written when the caller sets it: an absent depth means the entries
|
|
85
|
-
// are the directory's immediate children, which is the common case.
|
|
86
|
-
if (entriesDepth) record.entriesDepth = entriesDepth;
|
|
87
|
-
others.push(record);
|
|
88
|
-
fs.mkdirSync(home, { recursive: true });
|
|
89
|
-
fs.writeFileSync(file, JSON.stringify({ version: 1, caches: others }, null, 2));
|
|
90
|
-
} catch {
|
|
91
|
-
// A cache that cannot announce itself still works; it is just invisible.
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
function registerOnce(dir) {
|
|
96
|
-
registerCache({
|
|
97
|
-
dir,
|
|
98
|
-
name: 'Metro transform cache',
|
|
99
|
-
// One file per cache key, so entries nothing has touched can go
|
|
100
|
-
// individually rather than emptying the whole store. FileStore shards one
|
|
101
|
-
// level above them, so the entries are two deep.
|
|
102
|
-
prune: 'entries',
|
|
103
|
-
entriesDepth: 2,
|
|
104
|
-
note: 'shared Metro transforms; no eviction of its own',
|
|
105
|
-
});
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
// `name` only distinguishes one app's cache from another's on the same machine:
|
|
109
|
-
// it is a subdirectory of the shared root, not a directory of its own. Metro
|
|
110
|
-
// keys entries by content, so sharing one store between unrelated projects would
|
|
111
|
-
// be correct but pointlessly large.
|
|
112
|
-
function sharedCacheStores(name = 'app', { FileStore } = {}) {
|
|
113
|
-
const Store = FileStore || require('metro-cache').FileStore;
|
|
114
|
-
const root = cacheRoot(name);
|
|
115
|
-
registerOnce(root);
|
|
116
|
-
return [new Store({ root })];
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
// --- the NDJSON reporter ------------------------------------------------
|
|
120
|
-
//
|
|
121
|
-
// One JSON object per line, per rn-iso's log record contract:
|
|
122
|
-
//
|
|
123
|
-
// { ts, src: 'metro'|'client', level: 'debug'|'info'|'warn'|'error'|'fatal',
|
|
124
|
-
// msg, event?, stack?, marker? }
|
|
125
|
-
//
|
|
126
|
-
// Two files, because the two sources answer different questions: metro.ndjson
|
|
127
|
-
// is the bundler (did it build, what failed to transform) and client.ndjson is
|
|
128
|
-
// the app (what the running code logged and threw). Metro forwards the latter
|
|
129
|
-
// through the same reporter, so splitting here is what keeps `logs --source
|
|
130
|
-
// client` from being a grep over bundler chatter.
|
|
131
|
-
//
|
|
132
|
-
// The rules this file lives by: a logging failure must never become a dev
|
|
133
|
-
// server failure. Metro calls update() from inside its own build pipeline, so a
|
|
134
|
-
// throw here -- an event shape from a Metro version this package never saw, an
|
|
135
|
-
// unwritable log directory -- would take the server down with it. Every path
|
|
136
|
-
// swallows and counts instead.
|
|
137
|
-
|
|
138
|
-
const NDJSON_LEVELS = new Set(['debug', 'info', 'warn', 'error', 'fatal']);
|
|
139
|
-
|
|
140
|
-
// Metro speaks the console's vocabulary on client logs (`log`, `trace`,
|
|
141
|
-
// `group`) and its own on server logs. Anything unrecognized falls back to the
|
|
142
|
-
// caller's default rather than inventing a level.
|
|
143
|
-
function ndjsonLevel(level, fallback) {
|
|
144
|
-
const value = String(level === undefined || level === null ? '' : level).toLowerCase();
|
|
145
|
-
if (NDJSON_LEVELS.has(value)) return value;
|
|
146
|
-
switch (value) {
|
|
147
|
-
case 'log':
|
|
148
|
-
case 'dir':
|
|
149
|
-
case 'table':
|
|
150
|
-
case 'group':
|
|
151
|
-
case 'groupcollapsed':
|
|
152
|
-
case 'groupend':
|
|
153
|
-
return 'info';
|
|
154
|
-
case 'trace':
|
|
155
|
-
return 'debug';
|
|
156
|
-
case 'warning':
|
|
157
|
-
return 'warn';
|
|
158
|
-
default:
|
|
159
|
-
return fallback;
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
// Client logs arrive as the console's argument list, so they are joined the way
|
|
164
|
-
// a console would print them. A value that cannot be stringified (a circular
|
|
165
|
-
// object, a proxy that throws) still has to produce something.
|
|
166
|
-
function formatValue(value) {
|
|
167
|
-
if (typeof value === 'string') return value;
|
|
168
|
-
if (value instanceof Error) return value.message || String(value);
|
|
169
|
-
try {
|
|
170
|
-
const json = JSON.stringify(value);
|
|
171
|
-
if (json !== undefined) return json;
|
|
172
|
-
} catch {
|
|
173
|
-
// Fall through to String(), which handles circular structures.
|
|
174
|
-
}
|
|
175
|
-
try {
|
|
176
|
-
return String(value);
|
|
177
|
-
} catch {
|
|
178
|
-
return '[unprintable]';
|
|
179
|
-
}
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
function formatData(data) {
|
|
183
|
-
if (data === undefined || data === null) return '';
|
|
184
|
-
if (Array.isArray(data)) return data.map(formatValue).join(' ');
|
|
185
|
-
return formatValue(data);
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
// Metro wraps its failures differently depending on where they came from: a
|
|
189
|
-
// resolution failure is an Error, a transformer failure can be a plain object
|
|
190
|
-
// carrying only a message.
|
|
191
|
-
function errorMessage(error) {
|
|
192
|
-
if (error === undefined || error === null) return 'unknown error';
|
|
193
|
-
if (typeof error === 'string') return error;
|
|
194
|
-
if (typeof error.message === 'string' && error.message) return error.message;
|
|
195
|
-
return formatValue(error);
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
function ndjsonReporter({ dir } = {}) {
|
|
199
|
-
const logDir = dir || path.join(process.cwd(), '.rn-iso', 'logs');
|
|
200
|
-
let ensured = false;
|
|
201
|
-
let drops = 0;
|
|
202
|
-
|
|
203
|
-
// Lazily: constructing a reporter must not create directories for a server
|
|
204
|
-
// that may never start, and the log directory is workspace-local, so it may
|
|
205
|
-
// not exist yet at all.
|
|
206
|
-
function write(file, record) {
|
|
207
|
-
try {
|
|
208
|
-
if (!ensured) {
|
|
209
|
-
fs.mkdirSync(logDir, { recursive: true });
|
|
210
|
-
ensured = true;
|
|
211
|
-
}
|
|
212
|
-
fs.appendFileSync(path.join(logDir, file), JSON.stringify(record) + '\n');
|
|
213
|
-
} catch {
|
|
214
|
-
// An unwritable log directory is a housekeeping problem, not a build one.
|
|
215
|
-
// The count is what makes it visible instead of silent.
|
|
216
|
-
drops += 1;
|
|
217
|
-
ensured = false;
|
|
218
|
-
}
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
function update(event) {
|
|
222
|
-
try {
|
|
223
|
-
const type = event && typeof event.type === 'string' ? event.type : '';
|
|
224
|
-
const record = { ts: Date.now(), src: 'metro', level: 'debug', msg: '' };
|
|
225
|
-
if (type) record.event = type;
|
|
226
|
-
|
|
227
|
-
if (type === 'client_log') {
|
|
228
|
-
record.src = 'client';
|
|
229
|
-
record.level = ndjsonLevel(event.level, 'info');
|
|
230
|
-
record.msg = formatData(event.data);
|
|
231
|
-
// Passed through as-is: symbolication happens on the reading side, and
|
|
232
|
-
// a stack this reporter could not parse is still better than no stack.
|
|
233
|
-
if (event.stack) record.stack = event.stack;
|
|
234
|
-
write('client.ndjson', record);
|
|
235
|
-
return;
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
if (type === 'bundling_error' || type === 'transformer_error') {
|
|
239
|
-
record.level = 'error';
|
|
240
|
-
record.msg = errorMessage(event.error);
|
|
241
|
-
} else if (type === 'bundle_build_done') {
|
|
242
|
-
record.level = 'info';
|
|
243
|
-
record.msg = event.buildID ? `bundle build done (${event.buildID})` : 'bundle build done';
|
|
244
|
-
// The marker resets the window `rn-iso logs --errors` reports over: a
|
|
245
|
-
// successful build is the point past which older errors are history.
|
|
246
|
-
record.marker = true;
|
|
247
|
-
} else if (type === 'unstable_server_log') {
|
|
248
|
-
record.level = ndjsonLevel(event.level, 'info');
|
|
249
|
-
record.msg = formatData(event.data);
|
|
250
|
-
} else {
|
|
251
|
-
// Everything else is kept at debug rather than dropped: the event name
|
|
252
|
-
// is often the only evidence of what the server was doing before it
|
|
253
|
-
// failed, and debug costs nothing to a default query.
|
|
254
|
-
record.msg = formatData(event && event.data) || type || 'metro event';
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
write('metro.ndjson', record);
|
|
258
|
-
} catch {
|
|
259
|
-
// The event shape came from a package this one does not version.
|
|
260
|
-
drops += 1;
|
|
261
|
-
}
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
return {
|
|
265
|
-
dir: logDir,
|
|
266
|
-
update,
|
|
267
|
-
get drops() {
|
|
268
|
-
return drops;
|
|
269
|
-
},
|
|
270
|
-
};
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
module.exports = { sharedCacheStores, cacheRoot, ndjsonReporter };
|