everything-dev 0.1.2 → 0.1.4
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/package.json +11 -7
- package/src/cli.ts +109 -1
- package/src/components/monitor-view.tsx +471 -0
- package/src/contract.ts +94 -0
- package/src/lib/nova.ts +11 -11
- package/src/lib/orchestrator.ts +13 -2
- package/src/lib/process.ts +50 -5
- package/src/lib/resource-monitor/assertions.ts +234 -0
- package/src/lib/resource-monitor/command.ts +283 -0
- package/src/lib/resource-monitor/diff.ts +143 -0
- package/src/lib/resource-monitor/errors.ts +127 -0
- package/src/lib/resource-monitor/index.ts +305 -0
- package/src/lib/resource-monitor/platform/darwin.ts +293 -0
- package/src/lib/resource-monitor/platform/index.ts +35 -0
- package/src/lib/resource-monitor/platform/linux.ts +332 -0
- package/src/lib/resource-monitor/platform/windows.ts +298 -0
- package/src/lib/resource-monitor/snapshot.ts +204 -0
- package/src/lib/resource-monitor/types.ts +74 -0
- package/src/lib/session-recorder/errors.ts +102 -0
- package/src/lib/session-recorder/flows/login.ts +210 -0
- package/src/lib/session-recorder/index.ts +361 -0
- package/src/lib/session-recorder/playwright.ts +257 -0
- package/src/lib/session-recorder/report.ts +353 -0
- package/src/lib/session-recorder/server.ts +267 -0
- package/src/lib/session-recorder/types.ts +115 -0
- package/src/plugin.ts +161 -17
- package/src/types.ts +8 -0
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import type { Snapshot } from "../resource-monitor";
|
|
2
|
+
|
|
3
|
+
export interface SessionConfig {
|
|
4
|
+
ports: number[];
|
|
5
|
+
snapshotIntervalMs: number;
|
|
6
|
+
headless: boolean;
|
|
7
|
+
baseUrl: string;
|
|
8
|
+
timeout: number;
|
|
9
|
+
outputPath?: string;
|
|
10
|
+
devMode?: "local" | "remote";
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export interface BrowserMetrics {
|
|
14
|
+
jsHeapUsedSize: number;
|
|
15
|
+
jsHeapTotalSize: number;
|
|
16
|
+
documents: number;
|
|
17
|
+
frames: number;
|
|
18
|
+
jsEventListeners: number;
|
|
19
|
+
nodes: number;
|
|
20
|
+
layoutCount: number;
|
|
21
|
+
recalcStyleCount: number;
|
|
22
|
+
scriptDuration: number;
|
|
23
|
+
taskDuration: number;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export type SessionEventType =
|
|
27
|
+
| "baseline"
|
|
28
|
+
| "interval"
|
|
29
|
+
| "pageload"
|
|
30
|
+
| "navigation"
|
|
31
|
+
| "click"
|
|
32
|
+
| "popup_open"
|
|
33
|
+
| "popup_close"
|
|
34
|
+
| "auth_start"
|
|
35
|
+
| "auth_complete"
|
|
36
|
+
| "auth_failed"
|
|
37
|
+
| "error"
|
|
38
|
+
| "custom";
|
|
39
|
+
|
|
40
|
+
export interface SessionEvent {
|
|
41
|
+
id: string;
|
|
42
|
+
timestamp: number;
|
|
43
|
+
type: SessionEventType;
|
|
44
|
+
label: string;
|
|
45
|
+
snapshot: Snapshot;
|
|
46
|
+
browserMetrics?: BrowserMetrics;
|
|
47
|
+
url?: string;
|
|
48
|
+
error?: string;
|
|
49
|
+
metadata?: Record<string, unknown>;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export interface SessionSummary {
|
|
53
|
+
totalMemoryDeltaMb: number;
|
|
54
|
+
peakMemoryMb: number;
|
|
55
|
+
averageMemoryMb: number;
|
|
56
|
+
processesSpawned: number;
|
|
57
|
+
processesKilled: number;
|
|
58
|
+
orphanedProcesses: number;
|
|
59
|
+
portsUsed: number[];
|
|
60
|
+
portsLeaked: number;
|
|
61
|
+
hasLeaks: boolean;
|
|
62
|
+
eventCount: number;
|
|
63
|
+
duration: number;
|
|
64
|
+
browserMetricsSummary?: {
|
|
65
|
+
peakJsHeapMb: number;
|
|
66
|
+
averageJsHeapMb: number;
|
|
67
|
+
totalLayoutCount: number;
|
|
68
|
+
totalScriptDuration: number;
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export interface SessionReport {
|
|
73
|
+
sessionId: string;
|
|
74
|
+
config: SessionConfig;
|
|
75
|
+
startTime: number;
|
|
76
|
+
endTime: number;
|
|
77
|
+
events: SessionEvent[];
|
|
78
|
+
summary: SessionSummary;
|
|
79
|
+
platform: NodeJS.Platform;
|
|
80
|
+
nodeVersion: string;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export interface ServerHandle {
|
|
84
|
+
pid: number;
|
|
85
|
+
port: number;
|
|
86
|
+
name: string;
|
|
87
|
+
kill: () => Promise<void>;
|
|
88
|
+
waitForExit: (timeoutMs?: number) => Promise<number | null>;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export interface ServerOrchestrator {
|
|
92
|
+
handles: ServerHandle[];
|
|
93
|
+
ports: number[];
|
|
94
|
+
shutdown: () => Promise<void>;
|
|
95
|
+
waitForReady: () => Promise<boolean>;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export type SessionFlow = (context: FlowContext) => Promise<void>;
|
|
99
|
+
|
|
100
|
+
export interface FlowContext {
|
|
101
|
+
page: unknown;
|
|
102
|
+
context: unknown;
|
|
103
|
+
recordEvent: (type: SessionEventType, label: string, metadata?: Record<string, unknown>) => Promise<void>;
|
|
104
|
+
headless: boolean;
|
|
105
|
+
baseUrl: string;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
export const DEFAULT_SESSION_CONFIG: SessionConfig = {
|
|
109
|
+
ports: [3000, 3002, 3014],
|
|
110
|
+
snapshotIntervalMs: 2000,
|
|
111
|
+
headless: true,
|
|
112
|
+
baseUrl: "http://localhost:3000",
|
|
113
|
+
timeout: 120000,
|
|
114
|
+
devMode: "remote",
|
|
115
|
+
};
|
package/src/plugin.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { createPlugin } from "every-plugin";
|
|
2
2
|
import { Effect } from "every-plugin/effect";
|
|
3
3
|
import { z } from "every-plugin/zod";
|
|
4
|
-
import {
|
|
4
|
+
import { calculateRequiredDeposit, Graph } from "near-social-js";
|
|
5
5
|
|
|
6
|
-
import {
|
|
6
|
+
import { runMonitorCli } from "./components/monitor-view";
|
|
7
7
|
import {
|
|
8
8
|
type AppConfig,
|
|
9
9
|
type BosConfig as BosConfigType,
|
|
@@ -18,7 +18,7 @@ import {
|
|
|
18
18
|
loadConfig,
|
|
19
19
|
type RemoteConfig,
|
|
20
20
|
resolvePackageModes,
|
|
21
|
-
type SourceMode,
|
|
21
|
+
type SourceMode,
|
|
22
22
|
setConfig
|
|
23
23
|
} from "./config";
|
|
24
24
|
import { bosContract } from "./contract";
|
|
@@ -37,6 +37,19 @@ import {
|
|
|
37
37
|
verifyNovaCredentials
|
|
38
38
|
} from "./lib/nova";
|
|
39
39
|
import { type AppOrchestrator, startApp } from "./lib/orchestrator";
|
|
40
|
+
import { createProcessRegistry } from "./lib/process-registry";
|
|
41
|
+
import {
|
|
42
|
+
createSnapshotWithPlatform,
|
|
43
|
+
formatSnapshotSummary,
|
|
44
|
+
runWithInfo
|
|
45
|
+
} from "./lib/resource-monitor";
|
|
46
|
+
import {
|
|
47
|
+
formatReportSummary,
|
|
48
|
+
navigateTo,
|
|
49
|
+
runLoginFlow,
|
|
50
|
+
runNavigationFlow,
|
|
51
|
+
SessionRecorder,
|
|
52
|
+
} from "./lib/session-recorder";
|
|
40
53
|
import { syncFiles } from "./lib/sync";
|
|
41
54
|
import { run } from "./utils/run";
|
|
42
55
|
import { colors, icons } from "./utils/theme";
|
|
@@ -76,8 +89,8 @@ function getSocialContract(network: "mainnet" | "testnet"): string {
|
|
|
76
89
|
}
|
|
77
90
|
|
|
78
91
|
function getSocialExplorerUrl(network: "mainnet" | "testnet", path: string): string {
|
|
79
|
-
const baseUrl = network === "testnet"
|
|
80
|
-
? "https://test.near.social"
|
|
92
|
+
const baseUrl = network === "testnet"
|
|
93
|
+
? "https://test.near.social"
|
|
81
94
|
: "https://near.social";
|
|
82
95
|
return `${baseUrl}/${path}`;
|
|
83
96
|
}
|
|
@@ -359,7 +372,7 @@ export default createPlugin({
|
|
|
359
372
|
|
|
360
373
|
serve: builder.serve.handler(async ({ input }) => {
|
|
361
374
|
const port = input.port;
|
|
362
|
-
|
|
375
|
+
|
|
363
376
|
return {
|
|
364
377
|
status: "serving" as const,
|
|
365
378
|
url: `http://localhost:${port}`,
|
|
@@ -481,7 +494,7 @@ export default createPlugin({
|
|
|
481
494
|
};
|
|
482
495
|
const argsBase64 = Buffer.from(JSON.stringify(socialArgs)).toString("base64");
|
|
483
496
|
|
|
484
|
-
const graph = new Graph({
|
|
497
|
+
const graph = new Graph({
|
|
485
498
|
network,
|
|
486
499
|
contractId: socialContract,
|
|
487
500
|
});
|
|
@@ -756,7 +769,12 @@ export default createPlugin({
|
|
|
756
769
|
const novaConfig = yield* getNovaConfig;
|
|
757
770
|
const nova = createNovaClient(novaConfig);
|
|
758
771
|
|
|
759
|
-
|
|
772
|
+
const gatewayNovaAccount = bosConfig.gateway?.nova?.account;
|
|
773
|
+
if (!gatewayNovaAccount) {
|
|
774
|
+
return yield* Effect.fail(new Error("gateway.nova.account is required for secrets registration"));
|
|
775
|
+
}
|
|
776
|
+
|
|
777
|
+
yield* registerSecretsGroup(nova, fullAccount, gatewayNovaAccount);
|
|
760
778
|
|
|
761
779
|
return {
|
|
762
780
|
status: "registered" as const,
|
|
@@ -1101,7 +1119,7 @@ export default createPlugin({
|
|
|
1101
1119
|
|
|
1102
1120
|
try {
|
|
1103
1121
|
const gatewayDomain = getGatewayDomain(bosConfig);
|
|
1104
|
-
const gatewayAccount = bosConfig.account;
|
|
1122
|
+
const gatewayAccount = bosConfig.gateway?.account || bosConfig.account;
|
|
1105
1123
|
|
|
1106
1124
|
const wranglerContent = await Bun.file(wranglerPath).text();
|
|
1107
1125
|
|
|
@@ -1356,11 +1374,11 @@ export default createPlugin({
|
|
|
1356
1374
|
|
|
1357
1375
|
const mergeAppConfig = (localApp: Record<string, unknown>, remoteApp: Record<string, unknown>): Record<string, unknown> => {
|
|
1358
1376
|
const merged: Record<string, unknown> = {};
|
|
1359
|
-
|
|
1377
|
+
|
|
1360
1378
|
for (const key of Object.keys(remoteApp)) {
|
|
1361
1379
|
const local = localApp[key] as Record<string, unknown> | undefined;
|
|
1362
1380
|
const remote = remoteApp[key] as Record<string, unknown>;
|
|
1363
|
-
|
|
1381
|
+
|
|
1364
1382
|
if (!local) {
|
|
1365
1383
|
merged[key] = remote;
|
|
1366
1384
|
continue;
|
|
@@ -1379,7 +1397,7 @@ export default createPlugin({
|
|
|
1379
1397
|
},
|
|
1380
1398
|
};
|
|
1381
1399
|
}
|
|
1382
|
-
|
|
1400
|
+
|
|
1383
1401
|
return merged;
|
|
1384
1402
|
};
|
|
1385
1403
|
|
|
@@ -1603,7 +1621,7 @@ export default createPlugin({
|
|
|
1603
1621
|
const port = input.port || (input.target === "development" ? 4000 : 3000);
|
|
1604
1622
|
|
|
1605
1623
|
const args = ["run"];
|
|
1606
|
-
|
|
1624
|
+
|
|
1607
1625
|
if (input.detach) {
|
|
1608
1626
|
args.push("-d");
|
|
1609
1627
|
}
|
|
@@ -1625,8 +1643,8 @@ export default createPlugin({
|
|
|
1625
1643
|
args.push("-e", `BOS_ACCOUNT=${bosConfig.account}`);
|
|
1626
1644
|
const gateway = bosConfig.gateway as { production?: string } | string | undefined;
|
|
1627
1645
|
if (gateway) {
|
|
1628
|
-
const domain = typeof gateway === "string"
|
|
1629
|
-
? gateway
|
|
1646
|
+
const domain = typeof gateway === "string"
|
|
1647
|
+
? gateway
|
|
1630
1648
|
: gateway.production?.replace(/^https?:\/\//, "") || "";
|
|
1631
1649
|
if (domain) {
|
|
1632
1650
|
args.push("-e", `GATEWAY_DOMAIN=${domain}`);
|
|
@@ -1683,14 +1701,14 @@ export default createPlugin({
|
|
|
1683
1701
|
stopped.push(input.containerId!);
|
|
1684
1702
|
} else if (input.all) {
|
|
1685
1703
|
const imageName = bosConfig?.account?.replace(/\./g, "-") || "bos-app";
|
|
1686
|
-
|
|
1704
|
+
|
|
1687
1705
|
const psResult = yield* Effect.tryPromise({
|
|
1688
1706
|
try: () => execa("docker", ["ps", "-q", "--filter", `ancestor=${imageName}`]),
|
|
1689
1707
|
catch: () => new Error("Failed to list containers"),
|
|
1690
1708
|
});
|
|
1691
1709
|
|
|
1692
1710
|
const containerIds = psResult.stdout.trim().split("\n").filter(Boolean);
|
|
1693
|
-
|
|
1711
|
+
|
|
1694
1712
|
for (const id of containerIds) {
|
|
1695
1713
|
yield* Effect.tryPromise({
|
|
1696
1714
|
try: () => execa("docker", ["stop", id]),
|
|
@@ -1716,6 +1734,132 @@ export default createPlugin({
|
|
|
1716
1734
|
};
|
|
1717
1735
|
}
|
|
1718
1736
|
}),
|
|
1737
|
+
|
|
1738
|
+
monitor: builder.monitor.handler(async ({ input }) => {
|
|
1739
|
+
try {
|
|
1740
|
+
if (input.json) {
|
|
1741
|
+
const snapshot = await runWithInfo(
|
|
1742
|
+
createSnapshotWithPlatform(input.ports ? { ports: input.ports } : undefined)
|
|
1743
|
+
);
|
|
1744
|
+
return {
|
|
1745
|
+
status: "snapshot" as const,
|
|
1746
|
+
snapshot: snapshot as any,
|
|
1747
|
+
};
|
|
1748
|
+
}
|
|
1749
|
+
|
|
1750
|
+
if (input.watch) {
|
|
1751
|
+
runMonitorCli({ ports: input.ports, json: false });
|
|
1752
|
+
return {
|
|
1753
|
+
status: "watching" as const,
|
|
1754
|
+
};
|
|
1755
|
+
}
|
|
1756
|
+
|
|
1757
|
+
const snapshot = await runWithInfo(
|
|
1758
|
+
createSnapshotWithPlatform(input.ports ? { ports: input.ports } : undefined)
|
|
1759
|
+
);
|
|
1760
|
+
console.log(formatSnapshotSummary(snapshot));
|
|
1761
|
+
|
|
1762
|
+
return {
|
|
1763
|
+
status: "snapshot" as const,
|
|
1764
|
+
snapshot: snapshot as any,
|
|
1765
|
+
};
|
|
1766
|
+
} catch (error) {
|
|
1767
|
+
return {
|
|
1768
|
+
status: "error" as const,
|
|
1769
|
+
error: error instanceof Error ? error.message : "Unknown error",
|
|
1770
|
+
};
|
|
1771
|
+
}
|
|
1772
|
+
}),
|
|
1773
|
+
|
|
1774
|
+
session: builder.session.handler(async ({ input }) => {
|
|
1775
|
+
const sessionEffect = Effect.gen(function* () {
|
|
1776
|
+
const recorder = yield* SessionRecorder.create({
|
|
1777
|
+
ports: [3000],
|
|
1778
|
+
snapshotIntervalMs: input.snapshotInterval,
|
|
1779
|
+
headless: input.headless,
|
|
1780
|
+
baseUrl: "http://localhost:3000",
|
|
1781
|
+
timeout: input.timeout,
|
|
1782
|
+
});
|
|
1783
|
+
|
|
1784
|
+
try {
|
|
1785
|
+
yield* recorder.startServers("start");
|
|
1786
|
+
|
|
1787
|
+
yield* recorder.startRecording();
|
|
1788
|
+
|
|
1789
|
+
const browser = yield* recorder.launchBrowser();
|
|
1790
|
+
|
|
1791
|
+
if (input.flow === "login") {
|
|
1792
|
+
yield* runLoginFlow(browser, {
|
|
1793
|
+
recordEvent: (type, label, metadata) =>
|
|
1794
|
+
recorder.recordEvent(type, label, metadata).pipe(
|
|
1795
|
+
Effect.asVoid,
|
|
1796
|
+
Effect.catchAll(() => Effect.void)
|
|
1797
|
+
),
|
|
1798
|
+
}, {
|
|
1799
|
+
baseUrl: "http://localhost:3000",
|
|
1800
|
+
headless: input.headless,
|
|
1801
|
+
stubWallet: input.headless,
|
|
1802
|
+
timeout: 30000,
|
|
1803
|
+
});
|
|
1804
|
+
} else if (input.flow === "navigation" && input.routes) {
|
|
1805
|
+
yield* runNavigationFlow(
|
|
1806
|
+
browser,
|
|
1807
|
+
{
|
|
1808
|
+
recordEvent: (type, label, metadata) =>
|
|
1809
|
+
recorder.recordEvent(type, label, metadata).pipe(
|
|
1810
|
+
Effect.asVoid,
|
|
1811
|
+
Effect.catchAll(() => Effect.void)
|
|
1812
|
+
),
|
|
1813
|
+
},
|
|
1814
|
+
input.routes,
|
|
1815
|
+
"http://localhost:3000"
|
|
1816
|
+
);
|
|
1817
|
+
} else {
|
|
1818
|
+
yield* navigateTo(browser.page, "http://localhost:3000");
|
|
1819
|
+
yield* Effect.sleep("5 seconds");
|
|
1820
|
+
}
|
|
1821
|
+
|
|
1822
|
+
yield* recorder.cleanup();
|
|
1823
|
+
|
|
1824
|
+
const report = yield* recorder.stopRecording();
|
|
1825
|
+
|
|
1826
|
+
yield* recorder.exportReport(input.output, input.format);
|
|
1827
|
+
|
|
1828
|
+
console.log(formatReportSummary(report));
|
|
1829
|
+
|
|
1830
|
+
return {
|
|
1831
|
+
status: report.summary.hasLeaks ? "leaks_detected" as const : "completed" as const,
|
|
1832
|
+
sessionId: recorder.getSessionId(),
|
|
1833
|
+
reportPath: input.output,
|
|
1834
|
+
summary: {
|
|
1835
|
+
totalMemoryDeltaMb: report.summary.totalMemoryDeltaMb,
|
|
1836
|
+
peakMemoryMb: report.summary.peakMemoryMb,
|
|
1837
|
+
averageMemoryMb: report.summary.averageMemoryMb,
|
|
1838
|
+
processesSpawned: report.summary.processesSpawned,
|
|
1839
|
+
processesKilled: report.summary.processesKilled,
|
|
1840
|
+
orphanedProcesses: report.summary.orphanedProcesses,
|
|
1841
|
+
portsUsed: report.summary.portsUsed,
|
|
1842
|
+
portsLeaked: report.summary.portsLeaked,
|
|
1843
|
+
hasLeaks: report.summary.hasLeaks,
|
|
1844
|
+
eventCount: report.summary.eventCount,
|
|
1845
|
+
duration: report.summary.duration,
|
|
1846
|
+
},
|
|
1847
|
+
};
|
|
1848
|
+
} catch (error) {
|
|
1849
|
+
yield* recorder.cleanup();
|
|
1850
|
+
throw error;
|
|
1851
|
+
}
|
|
1852
|
+
});
|
|
1853
|
+
|
|
1854
|
+
try {
|
|
1855
|
+
return await Effect.runPromise(sessionEffect);
|
|
1856
|
+
} catch (error) {
|
|
1857
|
+
return {
|
|
1858
|
+
status: "error" as const,
|
|
1859
|
+
error: error instanceof Error ? error.message : "Unknown error",
|
|
1860
|
+
};
|
|
1861
|
+
}
|
|
1862
|
+
}),
|
|
1719
1863
|
}),
|
|
1720
1864
|
});
|
|
1721
1865
|
|
package/src/types.ts
CHANGED
|
@@ -30,9 +30,16 @@ export const RemoteConfigSchema = z.object({
|
|
|
30
30
|
});
|
|
31
31
|
export type RemoteConfig = z.infer<typeof RemoteConfigSchema>;
|
|
32
32
|
|
|
33
|
+
export const NovaConfigSchema = z.object({
|
|
34
|
+
account: z.string(),
|
|
35
|
+
});
|
|
36
|
+
export type NovaConfig = z.infer<typeof NovaConfigSchema>;
|
|
37
|
+
|
|
33
38
|
export const GatewayConfigSchema = z.object({
|
|
34
39
|
development: z.string(),
|
|
35
40
|
production: z.string(),
|
|
41
|
+
account: z.string().optional(),
|
|
42
|
+
nova: NovaConfigSchema.optional(),
|
|
36
43
|
});
|
|
37
44
|
export type GatewayConfig = z.infer<typeof GatewayConfigSchema>;
|
|
38
45
|
|
|
@@ -54,6 +61,7 @@ export type SyncConfig = z.infer<typeof SyncConfigSchema>;
|
|
|
54
61
|
export const BosConfigSchema = z.object({
|
|
55
62
|
account: z.string(),
|
|
56
63
|
testnet: z.string().optional(),
|
|
64
|
+
nova: NovaConfigSchema.optional(),
|
|
57
65
|
gateway: GatewayConfigSchema,
|
|
58
66
|
template: z.string().optional(),
|
|
59
67
|
cli: z.object({
|