@neat.is/core 0.5.0 → 0.5.1-dev.20260719
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/{chunk-QQDZSIY3.js → chunk-BI3XKGVG.js} +62 -2
- package/dist/chunk-BI3XKGVG.js.map +1 -0
- package/dist/{chunk-5MGHWP6R.js → chunk-O4PCNQFG.js} +39 -12
- package/dist/chunk-O4PCNQFG.js.map +1 -0
- package/dist/cli.cjs +96 -31
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +2 -2
- package/dist/index.cjs +111 -24
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +2 -2
- package/dist/neatd.cjs +111 -24
- package/dist/neatd.cjs.map +1 -1
- package/dist/neatd.js +2 -2
- package/dist/server.cjs +61 -1
- package/dist/server.cjs.map +1 -1
- package/dist/server.js +1 -1
- package/package.json +2 -2
- package/dist/chunk-5MGHWP6R.js.map +0 -1
- package/dist/chunk-QQDZSIY3.js.map +0 -1
package/dist/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
routeSpanToProject,
|
|
3
3
|
startDaemon
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-O4PCNQFG.js";
|
|
5
5
|
import {
|
|
6
6
|
ProjectNameCollisionError,
|
|
7
7
|
addProject,
|
|
@@ -37,7 +37,7 @@ import {
|
|
|
37
37
|
thresholdForEdgeType,
|
|
38
38
|
touchLastSeen,
|
|
39
39
|
writeAtomically
|
|
40
|
-
} from "./chunk-
|
|
40
|
+
} from "./chunk-BI3XKGVG.js";
|
|
41
41
|
import {
|
|
42
42
|
startOtelGrpcReceiver
|
|
43
43
|
} from "./chunk-I72HTUOG.js";
|
package/dist/neatd.cjs
CHANGED
|
@@ -2627,6 +2627,7 @@ function ensureFrontierNode(graph, host, ts) {
|
|
|
2627
2627
|
}
|
|
2628
2628
|
function upsertObservedEdge(graph, type, source, target, ts, isError = false, evidence) {
|
|
2629
2629
|
if (!graph.hasNode(source) || !graph.hasNode(target)) return null;
|
|
2630
|
+
const grain = source.startsWith("file:") ? "file" : "service";
|
|
2630
2631
|
const id = makeObservedEdgeId(type, source, target);
|
|
2631
2632
|
if (graph.hasEdge(id)) {
|
|
2632
2633
|
const existing = graph.getEdgeAttributes(id);
|
|
@@ -2643,7 +2644,9 @@ function upsertObservedEdge(graph, type, source, target, ts, isError = false, ev
|
|
|
2643
2644
|
lastObserved: ts,
|
|
2644
2645
|
callCount: newSpanCount,
|
|
2645
2646
|
signal: newSignal,
|
|
2646
|
-
confidence: (0, import_types3.confidenceForObservedSignal)(newSignal)
|
|
2647
|
+
confidence: (0, import_types3.confidenceForObservedSignal)(newSignal),
|
|
2648
|
+
grain
|
|
2649
|
+
// backfills legacy edges that predate ADR-142
|
|
2647
2650
|
};
|
|
2648
2651
|
graph.replaceEdgeAttributes(id, updated);
|
|
2649
2652
|
return { edge: updated, created: false };
|
|
@@ -2663,6 +2666,7 @@ function upsertObservedEdge(graph, type, source, target, ts, isError = false, ev
|
|
|
2663
2666
|
lastObserved: ts,
|
|
2664
2667
|
callCount: 1,
|
|
2665
2668
|
signal,
|
|
2669
|
+
grain,
|
|
2666
2670
|
// Call-site evidence from span code.* semconv (file-awareness.md §4 + §6).
|
|
2667
2671
|
// Only set when code.filepath was present on the span — never fabricated.
|
|
2668
2672
|
...evidence ? { evidence } : {}
|
|
@@ -4746,6 +4750,15 @@ async function parse4(serviceDir) {
|
|
|
4746
4750
|
const config = parseConnectionString(urlMatch[1]);
|
|
4747
4751
|
if (config) return [{ ...config, sourceFile: filePath }];
|
|
4748
4752
|
}
|
|
4753
|
+
const urlEnvMatch = content.match(
|
|
4754
|
+
/(?:url|connectionString)\s*:\s*process\.env(?:\.([A-Za-z_$][\w$]*)|\[\s*['"]([^'"]+)['"]\s*\])/
|
|
4755
|
+
);
|
|
4756
|
+
if (urlEnvMatch) {
|
|
4757
|
+
const varName = urlEnvMatch[1] ?? urlEnvMatch[2];
|
|
4758
|
+
const resolved = varName ? await resolveEnvVar(serviceDir, varName) : null;
|
|
4759
|
+
const config = resolved ? parseConnectionString(resolved) : null;
|
|
4760
|
+
if (config) return [{ ...config, sourceFile: filePath }];
|
|
4761
|
+
}
|
|
4749
4762
|
const hostMatch = content.match(/host\s*:\s*['"`]([^'"`]+)['"`]/);
|
|
4750
4763
|
if (hostMatch) {
|
|
4751
4764
|
const portMatch = content.match(/port\s*:\s*(\d+)/);
|
|
@@ -4799,6 +4812,15 @@ async function parse5(serviceDir) {
|
|
|
4799
4812
|
const config = parseConnectionString(urlMatch[1]);
|
|
4800
4813
|
if (config) return [{ ...config, sourceFile: filePath }];
|
|
4801
4814
|
}
|
|
4815
|
+
const urlEnvMatch = content.match(
|
|
4816
|
+
/connection\s*:\s*process\.env(?:\.([A-Za-z_$][\w$]*)|\[\s*['"]([^'"]+)['"]\s*\])/
|
|
4817
|
+
);
|
|
4818
|
+
if (urlEnvMatch) {
|
|
4819
|
+
const varName = urlEnvMatch[1] ?? urlEnvMatch[2];
|
|
4820
|
+
const resolved = varName ? await resolveEnvVar(serviceDir, varName) : null;
|
|
4821
|
+
const config = resolved ? parseConnectionString(resolved) : null;
|
|
4822
|
+
if (config) return [{ ...config, sourceFile: filePath }];
|
|
4823
|
+
}
|
|
4802
4824
|
const host = content.match(/host\s*:\s*['"`]([^'"`]+)['"`]/)?.[1];
|
|
4803
4825
|
if (host) {
|
|
4804
4826
|
const port = content.match(/port\s*:\s*(\d+)/)?.[1];
|
|
@@ -6347,6 +6369,16 @@ function constructorMatchesImport(name, ctx) {
|
|
|
6347
6369
|
if (name === "createClient") return ctx.hasSupabaseJs;
|
|
6348
6370
|
return ctx.hasSupabaseSsr;
|
|
6349
6371
|
}
|
|
6372
|
+
var SUPABASE_CLIENT_ASSIGN_RE = /(?:const|let|var)\s+(\w+)\s*=\s*(?:await\s+)?(createClient|createServerClient|createBrowserClient)\s*\(/g;
|
|
6373
|
+
function supabaseClientVars(content, ctx) {
|
|
6374
|
+
const vars = /* @__PURE__ */ new Set();
|
|
6375
|
+
SUPABASE_CLIENT_ASSIGN_RE.lastIndex = 0;
|
|
6376
|
+
let m;
|
|
6377
|
+
while ((m = SUPABASE_CLIENT_ASSIGN_RE.exec(content)) !== null) {
|
|
6378
|
+
if (constructorMatchesImport(m[2], ctx)) vars.add(m[1]);
|
|
6379
|
+
}
|
|
6380
|
+
return vars;
|
|
6381
|
+
}
|
|
6350
6382
|
function supabaseEndpointsFromFile(file, serviceDir) {
|
|
6351
6383
|
const ctx = readImports2(file.content);
|
|
6352
6384
|
if (!ctx.hasSupabaseJs && !ctx.hasSupabaseSsr) return [];
|
|
@@ -6379,6 +6411,34 @@ function supabaseEndpointsFromFile(file, serviceDir) {
|
|
|
6379
6411
|
}
|
|
6380
6412
|
});
|
|
6381
6413
|
}
|
|
6414
|
+
const clientVars = supabaseClientVars(file.content, ctx);
|
|
6415
|
+
for (const clientVar of clientVars) {
|
|
6416
|
+
const accessRe = new RegExp(
|
|
6417
|
+
`\\b${clientVar}\\s*\\.\\s*(from|rpc)\\s*\\(\\s*['"\`]([\\w.-]+)['"\`]`,
|
|
6418
|
+
"g"
|
|
6419
|
+
);
|
|
6420
|
+
let am;
|
|
6421
|
+
while ((am = accessRe.exec(file.content)) !== null) {
|
|
6422
|
+
const kind = am[1] === "rpc" ? "supabase-rpc" : "supabase-table";
|
|
6423
|
+
const resource = am[2];
|
|
6424
|
+
const key = `${kind}/${resource}`;
|
|
6425
|
+
if (seen.has(key)) continue;
|
|
6426
|
+
seen.add(key);
|
|
6427
|
+
const line = lineOf(file.content, am[0]);
|
|
6428
|
+
out.push({
|
|
6429
|
+
infraId: (0, import_types19.infraId)(kind, resource),
|
|
6430
|
+
name: resource,
|
|
6431
|
+
kind,
|
|
6432
|
+
edgeType: "CALLS",
|
|
6433
|
+
confidenceKind: "verified-call-site",
|
|
6434
|
+
evidence: {
|
|
6435
|
+
file: import_node_path30.default.relative(serviceDir, file.path),
|
|
6436
|
+
line,
|
|
6437
|
+
snippet: snippet(file.content, line)
|
|
6438
|
+
}
|
|
6439
|
+
});
|
|
6440
|
+
}
|
|
6441
|
+
}
|
|
6382
6442
|
return out;
|
|
6383
6443
|
}
|
|
6384
6444
|
|
|
@@ -9558,7 +9618,30 @@ init_otel();
|
|
|
9558
9618
|
|
|
9559
9619
|
// src/connectors/index.ts
|
|
9560
9620
|
init_cjs_shims();
|
|
9621
|
+
var import_types34 = require("@neat.is/types");
|
|
9561
9622
|
var NO_ENV = "unknown";
|
|
9623
|
+
function staticCallSiteFor(graph, serviceName, targetNodeId) {
|
|
9624
|
+
if (!graph.hasNode(targetNodeId)) return void 0;
|
|
9625
|
+
const sites = [];
|
|
9626
|
+
for (const edgeId of graph.inboundEdges(targetNodeId)) {
|
|
9627
|
+
const edge = graph.getEdgeAttributes(edgeId);
|
|
9628
|
+
if (edge.provenance !== import_types34.Provenance.EXTRACTED) continue;
|
|
9629
|
+
const parsed = (0, import_types34.parseFileId)(edge.source);
|
|
9630
|
+
if (!parsed || parsed.service !== serviceName || !edge.evidence) continue;
|
|
9631
|
+
const site = { relPath: edge.evidence.file };
|
|
9632
|
+
if (edge.evidence.line !== void 0) site.line = edge.evidence.line;
|
|
9633
|
+
sites.push(site);
|
|
9634
|
+
}
|
|
9635
|
+
return sites.length === 1 ? sites[0] : void 0;
|
|
9636
|
+
}
|
|
9637
|
+
function routeCallSiteFor(graph, targetNodeId) {
|
|
9638
|
+
if (!graph.hasNode(targetNodeId)) return void 0;
|
|
9639
|
+
const attrs = graph.getNodeAttributes(targetNodeId);
|
|
9640
|
+
if (attrs.type !== import_types34.NodeType.RouteNode || !attrs.path) return void 0;
|
|
9641
|
+
const site = { relPath: attrs.path };
|
|
9642
|
+
if (attrs.line !== void 0) site.line = attrs.line;
|
|
9643
|
+
return site;
|
|
9644
|
+
}
|
|
9562
9645
|
async function runConnectorPoll(connector, ctx, graph, resolveTarget) {
|
|
9563
9646
|
const signals = await connector.poll(ctx);
|
|
9564
9647
|
let edgesCreated = 0;
|
|
@@ -9575,7 +9658,7 @@ async function runConnectorPoll(connector, ctx, graph, resolveTarget) {
|
|
|
9575
9658
|
ensureInfraNode(graph, kind, name, provider);
|
|
9576
9659
|
}
|
|
9577
9660
|
const serviceNodeId = ensureServiceNode(graph, resolved.serviceName, NO_ENV);
|
|
9578
|
-
const callSite = signal.callSite ? { relPath: signal.callSite.file, line: signal.callSite.line } :
|
|
9661
|
+
const callSite = signal.callSite ? { relPath: signal.callSite.file, line: signal.callSite.line } : routeCallSiteFor(graph, resolved.targetNodeId) ?? staticCallSiteFor(graph, resolved.serviceName, resolved.targetNodeId);
|
|
9579
9662
|
const sourceId = callSite ? ensureObservedFileNode(graph, resolved.serviceName, serviceNodeId, callSite) : serviceNodeId;
|
|
9580
9663
|
const evidence = callSite ? {
|
|
9581
9664
|
file: reconcileObservedRelPath(graph, resolved.serviceName, callSite.relPath),
|
|
@@ -10126,19 +10209,23 @@ async function fetchPgStatStatements(connectionString, limit = DEFAULT_STATEMENT
|
|
|
10126
10209
|
|
|
10127
10210
|
// src/connectors/supabase/resolve.ts
|
|
10128
10211
|
init_cjs_shims();
|
|
10129
|
-
var
|
|
10212
|
+
var import_types36 = require("@neat.is/types");
|
|
10130
10213
|
function createSupabaseResolveTarget(graph, config) {
|
|
10131
10214
|
return (signal, _ctx) => {
|
|
10132
10215
|
if (signal.targetKind !== SUPABASE_TABLE_TARGET_KIND && signal.targetKind !== SUPABASE_RPC_TARGET_KIND) {
|
|
10133
10216
|
return null;
|
|
10134
10217
|
}
|
|
10135
|
-
const subResourceId = (0,
|
|
10218
|
+
const subResourceId = (0, import_types36.infraId)(signal.targetKind, `${config.nodeRef}/${signal.targetName}`);
|
|
10136
10219
|
if (graph.hasNode(subResourceId)) {
|
|
10137
|
-
return { targetNodeId: subResourceId, serviceName: config.serviceName, edgeType:
|
|
10220
|
+
return { targetNodeId: subResourceId, serviceName: config.serviceName, edgeType: import_types36.EdgeType.CALLS };
|
|
10221
|
+
}
|
|
10222
|
+
const bareResourceId = (0, import_types36.infraId)(signal.targetKind, signal.targetName);
|
|
10223
|
+
if (graph.hasNode(bareResourceId)) {
|
|
10224
|
+
return { targetNodeId: bareResourceId, serviceName: config.serviceName, edgeType: import_types36.EdgeType.CALLS };
|
|
10138
10225
|
}
|
|
10139
|
-
const projectLevelId = (0,
|
|
10226
|
+
const projectLevelId = (0, import_types36.infraId)("supabase", config.nodeRef);
|
|
10140
10227
|
if (graph.hasNode(projectLevelId)) {
|
|
10141
|
-
return { targetNodeId: projectLevelId, serviceName: config.serviceName, edgeType:
|
|
10228
|
+
return { targetNodeId: projectLevelId, serviceName: config.serviceName, edgeType: import_types36.EdgeType.CALLS };
|
|
10142
10229
|
}
|
|
10143
10230
|
return null;
|
|
10144
10231
|
};
|
|
@@ -10231,7 +10318,7 @@ function createSupabaseConnector(graph, config, deps = {}) {
|
|
|
10231
10318
|
|
|
10232
10319
|
// src/connectors/railway/index.ts
|
|
10233
10320
|
init_cjs_shims();
|
|
10234
|
-
var
|
|
10321
|
+
var import_types40 = require("@neat.is/types");
|
|
10235
10322
|
|
|
10236
10323
|
// src/connectors/railway/client.ts
|
|
10237
10324
|
init_cjs_shims();
|
|
@@ -10357,7 +10444,7 @@ function buildRailwayRouteIndex(graph, serviceName) {
|
|
|
10357
10444
|
const out = [];
|
|
10358
10445
|
graph.forEachNode((_id, attrs) => {
|
|
10359
10446
|
const node = attrs;
|
|
10360
|
-
if (node.type !==
|
|
10447
|
+
if (node.type !== import_types40.NodeType.RouteNode) return;
|
|
10361
10448
|
const route = attrs;
|
|
10362
10449
|
if (route.service !== serviceName) return;
|
|
10363
10450
|
out.push({
|
|
@@ -10453,12 +10540,12 @@ function createRailwayResolveTarget(config) {
|
|
|
10453
10540
|
const serviceName = config.serviceNameById[config.serviceId];
|
|
10454
10541
|
if (!serviceName) return null;
|
|
10455
10542
|
if (signal.targetKind === ROUTE_TARGET_KIND) {
|
|
10456
|
-
return { targetNodeId: signal.targetName, serviceName, edgeType:
|
|
10543
|
+
return { targetNodeId: signal.targetName, serviceName, edgeType: import_types40.EdgeType.CALLS };
|
|
10457
10544
|
}
|
|
10458
10545
|
if (signal.targetKind === PEER_SERVICE_TARGET_KIND) {
|
|
10459
10546
|
const peerName = config.serviceNameById[signal.targetName];
|
|
10460
10547
|
if (!peerName) return null;
|
|
10461
|
-
return { targetNodeId: (0,
|
|
10548
|
+
return { targetNodeId: (0, import_types40.serviceId)(peerName), serviceName, edgeType: import_types40.EdgeType.CONNECTS_TO };
|
|
10462
10549
|
}
|
|
10463
10550
|
return null;
|
|
10464
10551
|
};
|
|
@@ -10645,7 +10732,7 @@ function mapLogEntriesToSignals(entries) {
|
|
|
10645
10732
|
|
|
10646
10733
|
// src/connectors/firebase/resolve.ts
|
|
10647
10734
|
init_cjs_shims();
|
|
10648
|
-
var
|
|
10735
|
+
var import_types41 = require("@neat.is/types");
|
|
10649
10736
|
function neatServiceNameFor(resourceType, resourceName, serviceMap) {
|
|
10650
10737
|
switch (resourceType) {
|
|
10651
10738
|
case "cloud_function":
|
|
@@ -10660,7 +10747,7 @@ function routeEntriesFor(graph, serviceName) {
|
|
|
10660
10747
|
const entries = [];
|
|
10661
10748
|
graph.forEachNode((_id, attrs) => {
|
|
10662
10749
|
const node = attrs;
|
|
10663
|
-
if (node.type !==
|
|
10750
|
+
if (node.type !== import_types41.NodeType.RouteNode) return;
|
|
10664
10751
|
const route = attrs;
|
|
10665
10752
|
if (route.service !== serviceName) return;
|
|
10666
10753
|
entries.push({
|
|
@@ -10692,7 +10779,7 @@ function createFirebaseResolveTarget(graph, serviceMap) {
|
|
|
10692
10779
|
return {
|
|
10693
10780
|
targetNodeId: match.routeNodeId,
|
|
10694
10781
|
serviceName,
|
|
10695
|
-
edgeType:
|
|
10782
|
+
edgeType: import_types41.EdgeType.CALLS
|
|
10696
10783
|
};
|
|
10697
10784
|
};
|
|
10698
10785
|
}
|
|
@@ -10719,7 +10806,7 @@ init_cjs_shims();
|
|
|
10719
10806
|
|
|
10720
10807
|
// src/connectors/cloudflare/connector.ts
|
|
10721
10808
|
init_cjs_shims();
|
|
10722
|
-
var
|
|
10809
|
+
var import_types43 = require("@neat.is/types");
|
|
10723
10810
|
|
|
10724
10811
|
// src/connectors/cloudflare/client.ts
|
|
10725
10812
|
init_cjs_shims();
|
|
@@ -10880,7 +10967,7 @@ function findTaggedWorkerFileNode(graph, workerName) {
|
|
|
10880
10967
|
graph.forEachNode((id, attrs) => {
|
|
10881
10968
|
if (found) return;
|
|
10882
10969
|
const a = attrs;
|
|
10883
|
-
if (a.type ===
|
|
10970
|
+
if (a.type === import_types43.NodeType.FileNode && a.platform === "cloudflare" && a.platformName === workerName) {
|
|
10884
10971
|
found = id;
|
|
10885
10972
|
}
|
|
10886
10973
|
});
|
|
@@ -10892,7 +10979,7 @@ function findMatchingRouteNode(graph, serviceName, method, path53) {
|
|
|
10892
10979
|
graph.forEachNode((id, attrs) => {
|
|
10893
10980
|
if (found) return;
|
|
10894
10981
|
const a = attrs;
|
|
10895
|
-
if (a.type !==
|
|
10982
|
+
if (a.type !== import_types43.NodeType.RouteNode || a.service !== serviceName) return;
|
|
10896
10983
|
if (!a.pathTemplate || normalizePathTemplate(a.pathTemplate) !== normalizedPath) return;
|
|
10897
10984
|
const routeMethod = (a.method ?? "").toUpperCase();
|
|
10898
10985
|
if (routeMethod !== "ALL" && routeMethod !== method) return;
|
|
@@ -10911,11 +10998,11 @@ function createCloudflareResolveTarget(config, graph) {
|
|
|
10911
10998
|
};
|
|
10912
10999
|
const mapping = config.workers?.[scriptName];
|
|
10913
11000
|
if (mapping) {
|
|
10914
|
-
const wholeFileId = (0,
|
|
11001
|
+
const wholeFileId = (0, import_types43.fileId)(mapping.service, mapping.entryFile);
|
|
10915
11002
|
return {
|
|
10916
11003
|
targetNodeId: resolveRouteGrain(mapping.service, wholeFileId),
|
|
10917
11004
|
serviceName: mapping.service,
|
|
10918
|
-
edgeType:
|
|
11005
|
+
edgeType: import_types43.EdgeType.CALLS
|
|
10919
11006
|
};
|
|
10920
11007
|
}
|
|
10921
11008
|
const taggedFileId = findTaggedWorkerFileNode(graph, scriptName);
|
|
@@ -10924,13 +11011,13 @@ function createCloudflareResolveTarget(config, graph) {
|
|
|
10924
11011
|
return {
|
|
10925
11012
|
targetNodeId: resolveRouteGrain(fileNode.service, taggedFileId),
|
|
10926
11013
|
serviceName: fileNode.service,
|
|
10927
|
-
edgeType:
|
|
11014
|
+
edgeType: import_types43.EdgeType.CALLS
|
|
10928
11015
|
};
|
|
10929
11016
|
}
|
|
10930
11017
|
return {
|
|
10931
|
-
targetNodeId: (0,
|
|
11018
|
+
targetNodeId: (0, import_types43.infraId)("cloudflare-worker", scriptName),
|
|
10932
11019
|
serviceName: scriptName,
|
|
10933
|
-
edgeType:
|
|
11020
|
+
edgeType: import_types43.EdgeType.CALLS,
|
|
10934
11021
|
ensureInfraNode: { kind: "cloudflare-worker", name: scriptName, provider: "cloudflare" }
|
|
10935
11022
|
};
|
|
10936
11023
|
};
|
|
@@ -11189,7 +11276,7 @@ function unroutedErrorsPath(neatHome4) {
|
|
|
11189
11276
|
}
|
|
11190
11277
|
|
|
11191
11278
|
// src/daemon.ts
|
|
11192
|
-
var
|
|
11279
|
+
var import_types46 = require("@neat.is/types");
|
|
11193
11280
|
function daemonJsonPath(scanPath) {
|
|
11194
11281
|
return import_node_path50.default.join(scanPath, "neat-out", "daemon.json");
|
|
11195
11282
|
}
|
|
@@ -11324,7 +11411,7 @@ function spanBelongsToSingleProject(graph, project, serviceName) {
|
|
|
11324
11411
|
if (!serviceName) return true;
|
|
11325
11412
|
if (serviceNameMatchesProject(serviceName, project)) return true;
|
|
11326
11413
|
return graph.someNode(
|
|
11327
|
-
(_id, attrs) => attrs.type ===
|
|
11414
|
+
(_id, attrs) => attrs.type === import_types46.NodeType.ServiceNode && attrs.name === serviceName
|
|
11328
11415
|
);
|
|
11329
11416
|
}
|
|
11330
11417
|
async function bootstrapProject(entry2, connectors = [], neatHome4) {
|