systemview 1.20.1 → 1.21.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.
@@ -1,80 +1,172 @@
1
- const fs = require("fs");
2
- const path = require("path");
3
- const { HttpClient } = require("systemlynx");
4
- const { createCookieClient } = require("./cookieClient");
5
- const Client = createCookieClient();
1
+ const { HttpClient, createClient } = require("systemlynx");
2
+ const { createCookieHttpClient } = require("./cookieClient");
3
+ const cookieHttpClient = createCookieHttpClient();
4
+ const Client = createClient(cookieHttpClient);
6
5
  const log = require("./logger");
7
6
 
8
- async function probeService(url) {
9
- const connectionData = await HttpClient.request({ url });
10
- if (!connectionData.SystemLynxService) {
11
- throw new Error(`No SystemLynx service found at ${url}`);
12
- }
13
- const client = Client.createService(connectionData);
14
- const connection = await client.Plugin.getConnection();
15
- return connection; // { projectCode, serviceId, system, specList }
16
- }
17
-
18
- function readManifest(manifestFile) {
19
- if (!fs.existsSync(manifestFile)) return null;
7
+ async function getUiSvc(uiUrl) {
20
8
  try {
21
- return JSON.parse(fs.readFileSync(manifestFile, "utf8"));
9
+ const { SystemView } = await Client.loadService(`${uiUrl}/systemview/api`);
10
+ return SystemView;
22
11
  } catch {
23
12
  return null;
24
13
  }
25
14
  }
26
15
 
27
- function writeManifest(manifestFile, connection) {
28
- const manifest = readManifest(manifestFile) || { projectCode: connection.projectCode, services: [] };
29
- if (!manifest.services) manifest.services = [];
30
- const entry = { serviceId: connection.serviceId, system: connection.system, specList: connection.specList };
31
- const idx = manifest.services.findIndex((s) => s.serviceId === connection.serviceId);
32
- if (idx > -1) manifest.services[idx] = entry;
33
- else manifest.services.push(entry);
34
- manifest.projectCode = connection.projectCode;
35
- fs.writeFileSync(manifestFile, JSON.stringify(manifest, null, 2));
16
+ async function notifyUi(uiSvc, project) {
17
+ if (!uiSvc) return;
18
+ try {
19
+ await uiSvc.connect(project);
20
+ } catch {}
21
+ }
22
+
23
+ function serviceIdFromRoute(route) {
24
+ const segments = (route || "").split("/").filter(Boolean);
25
+ return [...segments].reverse().find((s) => s.toLowerCase() !== "api") || "Service";
36
26
  }
37
27
 
38
- module.exports = async function connectService(serviceId, url, { manifest: manifestPath } = {}) {
39
- const manifestFile = manifestPath || path.join(process.cwd(), "systemview.manifest.json");
28
+ function printSummary(entries) {
29
+ console.log("");
30
+ entries.forEach(({ projectCode, serviceId, url }) => {
31
+ log.plain(` ${projectCode} › ${serviceId}${url ? " " + url : ""}`);
32
+ });
33
+ console.log("");
34
+ }
40
35
 
41
- if (!url) {
42
- // Re-probe all services already in the manifest
43
- const manifest = readManifest(manifestFile);
44
- if (!manifest || !manifest.services || !manifest.services.length) {
45
- log.warn("No manifest found. Use: systemview connect <serviceId> <url>");
36
+ module.exports = async function connectService(target, { useManifest, force, connectedUrls, uiUrl } = {}) {
37
+ const uiSvc = await getUiSvc(uiUrl);
38
+
39
+ // connect (no args) connect to every service stored in UI
40
+ if (!target) {
41
+ if (!uiSvc) {
42
+ log.warn("SystemView UI not running. Start it first or provide a URL.");
43
+ return;
44
+ }
45
+ const projects = await uiSvc.getProjects();
46
+ const all = Object.entries(projects).flatMap(([pc, svcs]) =>
47
+ svcs.map((s) => ({ projectCode: pc, ...s }))
48
+ );
49
+ if (!all.length) {
50
+ log.warn("No services in UI store. Use: systemview connect <url>");
46
51
  return;
47
52
  }
48
- log.info(`Re-probing ${manifest.services.length} service(s)...`);
49
- for (const { serviceId: id, system } of manifest.services) {
53
+ log.info(`Connecting to ${all.length} stored service(s)...`);
54
+ const summary = [];
55
+ for (const { projectCode, serviceId, connectionData, serviceUrl } of all) {
56
+ const url = serviceUrl || (connectionData && connectionData.serviceUrl);
57
+ if (!url) continue;
50
58
  try {
51
- const connection = await probeService(system.connectionData.serviceUrl);
52
- writeManifest(manifestFile, connection);
53
- log.success(`${id} updated`);
59
+ if (force) {
60
+ await Client.loadService(url);
61
+ } else {
62
+ Client.createService(connectionData);
63
+ }
64
+ connectedUrls.add(url);
65
+ log.success(`${projectCode} › ${serviceId}`);
66
+ summary.push({ projectCode, serviceId, url });
54
67
  } catch (err) {
55
- log.error(`${id} failed: ${err.message}`);
68
+ log.error(`${serviceId} failed: ${err.message}`);
56
69
  }
57
70
  }
58
- printManifestSummary(readManifest(manifestFile));
71
+ printSummary(summary);
59
72
  return;
60
73
  }
61
74
 
62
- log.info(`Connecting to ${serviceId || "service"} @ ${url}...`);
75
+ const isUrl = /^https?:\/\//i.test(target);
76
+
77
+ // connect <projectCode> — look up from UI store
78
+ if (!isUrl) {
79
+ if (!uiSvc) {
80
+ log.warn("SystemView UI not running. Cannot look up project by code.");
81
+ return;
82
+ }
83
+ const services = await uiSvc.getServices(target);
84
+ if (!services || !services.length) {
85
+ log.warn(`No services found for project: ${target}`);
86
+ return;
87
+ }
88
+ log.info(`Connecting to ${services.length} service(s) for ${target}...`);
89
+ const summary = [];
90
+ for (const { serviceId, system } of services) {
91
+ const { serviceUrl } = system.connectionData;
92
+ try {
93
+ if (force) {
94
+ await Client.loadService(serviceUrl);
95
+ } else {
96
+ Client.createService(system.connectionData);
97
+ }
98
+ connectedUrls.add(serviceUrl);
99
+ log.success(`${target} › ${serviceId}`);
100
+ summary.push({ projectCode: target, serviceId, url: serviceUrl });
101
+ } catch (err) {
102
+ log.error(`${serviceId} failed: ${err.message}`);
103
+ }
104
+ }
105
+ printSummary(summary);
106
+ return;
107
+ }
108
+
109
+ // connect <url> [--manifest]
110
+ log.info(`Connecting to ${target}...`);
63
111
  try {
64
- const connection = await probeService(url);
65
- writeManifest(manifestFile, connection);
66
- printManifestSummary(readManifest(manifestFile));
112
+ if (useManifest) {
113
+ const svc = await Client.loadService(target);
114
+ let manifest = null;
115
+ try { manifest = await svc.Plugin.getManifest(); } catch {}
116
+ if (manifest && manifest.services && manifest.services.length) {
117
+ log.info(`Plugin manifest found: ${manifest.projectCode} (${manifest.services.length} service(s))`);
118
+ const summary = [];
119
+ for (const service of manifest.services) {
120
+ try {
121
+ Client.createService(service.system.connectionData);
122
+ connectedUrls.add(service.system.connectionData.serviceUrl);
123
+ await notifyUi(uiSvc, {
124
+ system: service.system,
125
+ projectCode: manifest.projectCode,
126
+ serviceId: service.serviceId,
127
+ specList: service.specList || { tests: [], docs: [] },
128
+ });
129
+ log.success(`${manifest.projectCode} › ${service.serviceId}`);
130
+ summary.push({ projectCode: manifest.projectCode, serviceId: service.serviceId, url: service.system.connectionData.serviceUrl });
131
+ } catch (err) {
132
+ log.error(`${service.serviceId} failed: ${err.message}`);
133
+ }
134
+ }
135
+ printSummary(summary);
136
+ } else {
137
+ log.warn("No plugin manifest found. Storing as connected-services.");
138
+ const connectionData = await HttpClient.request({ url: target });
139
+ const serviceId = serviceIdFromRoute(connectionData.route);
140
+ const project = {
141
+ system: { connectionData },
142
+ projectCode: "connected-services",
143
+ serviceId,
144
+ specList: { tests: [], docs: [] },
145
+ };
146
+ Client.createService(connectionData);
147
+ connectedUrls.add(target);
148
+ await notifyUi(uiSvc, project);
149
+ printSummary([{ projectCode: "connected-services", serviceId, url: target }]);
150
+ }
151
+ } else {
152
+ const connectionData = await HttpClient.request({ url: target });
153
+ if (!connectionData || !connectionData.SystemLynxService) {
154
+ throw new Error("No SystemLynx service found at " + target);
155
+ }
156
+ const serviceId = serviceIdFromRoute(connectionData.route);
157
+ const project = {
158
+ system: { connectionData },
159
+ projectCode: "connected-services",
160
+ serviceId,
161
+ specList: { tests: [], docs: [] },
162
+ };
163
+ Client.createService(connectionData);
164
+ connectedUrls.add(target);
165
+ await notifyUi(uiSvc, project);
166
+ log.success(`connected-services › ${serviceId}`);
167
+ printSummary([{ projectCode: "connected-services", serviceId, url: target }]);
168
+ }
67
169
  } catch (err) {
68
170
  log.error(`Connection failed: ${err.message}`);
69
171
  }
70
172
  };
71
-
72
- function printManifestSummary(manifest) {
73
- if (!manifest) return;
74
- log.plain(`\n Project: ${manifest.projectCode}`);
75
- log.plain(` Services:`);
76
- (manifest.services || []).forEach(({ serviceId, system }) => {
77
- log.plain(` - ${serviceId} @ ${system.connectionData.serviceUrl}`);
78
- });
79
- log.plain("");
80
- }
@@ -87,7 +87,4 @@ function createCookieHttpClient(extraHeaders = {}) {
87
87
  };
88
88
  }
89
89
 
90
- module.exports = {
91
- createCookieHttpClient,
92
- createCookieClient: (extraHeaders = {}) => createClient(createCookieHttpClient(extraHeaders)),
93
- };
90
+ module.exports = { createCookieHttpClient };
package/cli/index.js CHANGED
@@ -13,17 +13,39 @@ const listTests = require("./listTests");
13
13
  const appIsRunning = require("./appIsRunning");
14
14
  const openBrowser = require("./openBrowser");
15
15
  const connectService = require("./connectService");
16
+ const manifestCommands = require("./manifest");
16
17
  const probe = require("./probe");
17
- const { HttpClient } = require("systemlynx");
18
- const { createCookieClient } = require("./cookieClient");
18
+ const logsCommand = require("./logs");
19
+ const { HttpClient, createClient } = require("systemlynx");
20
+ const { createCookieHttpClient } = require("./cookieClient");
21
+ const cookieHttpClient = createCookieHttpClient();
22
+ const Client = createClient(cookieHttpClient);
19
23
 
20
24
  const DEFAULT_PORT = 3000;
21
25
  const VERSION = require("../package.json").version;
26
+ const UI_URL = `http://localhost:${DEFAULT_PORT}`;
27
+
28
+ const MANIFEST_FILE = flags.manifest || path.join(process.cwd(), "systemview.manifest.json");
29
+ const connectedUrls = new Set();
30
+
31
+ function loadManifest() {
32
+ if (!fs.existsSync(MANIFEST_FILE)) return;
33
+ try {
34
+ const manifest = JSON.parse(fs.readFileSync(MANIFEST_FILE, "utf8"));
35
+ const services = manifest.services || [manifest];
36
+ for (const { system } of services) {
37
+ if (system && system.connectionData) {
38
+ Client.createService(system.connectionData);
39
+ connectedUrls.add(system.connectionData.serviceUrl);
40
+ }
41
+ }
42
+ } catch {}
43
+ }
22
44
 
23
45
  async function startApp() {
24
46
  const port = isNaN(input[1]) ? DEFAULT_PORT : Number(input[1]);
25
47
  try {
26
- await launchApp(port, { interactive: true });
48
+ await launchApp(port, { interactive: true, connectedUrls });
27
49
  } catch (error) {
28
50
  log.error("Launch failed: " + error.message);
29
51
  }
@@ -32,13 +54,11 @@ async function startApp() {
32
54
  async function startTest() {
33
55
  const project_code = input[1];
34
56
  const namespace = input[2];
35
- const url = `http://localhost:${DEFAULT_PORT}`;
36
57
  try {
37
58
  await launchApp(DEFAULT_PORT);
38
- const exitCode = await runTests(url, project_code, namespace, {
59
+ const exitCode = await runTests(UI_URL, project_code, namespace, {
39
60
  json: flags.json,
40
61
  verbose: flags.verbose,
41
- manifest: flags.manifest,
42
62
  headers: flags.headers,
43
63
  bail: flags.bail,
44
64
  dryRun: flags.dryRun,
@@ -56,41 +76,34 @@ async function startTest() {
56
76
  async function list() {
57
77
  const project_code = input[1];
58
78
  const namespace = input[2];
59
- const url = `http://localhost:${DEFAULT_PORT}`;
60
- const api = `${url}/systemview/api`;
79
+ const api = `${UI_URL}/systemview/api`;
61
80
  if (!(await appIsRunning(api))) {
62
81
  await launchApp(DEFAULT_PORT);
63
82
  }
64
- await listTests(url, project_code, namespace, { manifest: flags.manifest, verbose: flags.verbose });
83
+ await listTests(UI_URL, project_code, namespace, {
84
+ connectedUrls,
85
+ verbose: flags.verbose,
86
+ });
65
87
  process.exit(0);
66
88
  }
67
89
 
68
90
  async function open() {
69
91
  const project_code = input[1];
70
92
  const namespace = input[2];
71
- const ui = `http://localhost:${DEFAULT_PORT}`;
72
- const api = `${ui}/systemview/api`;
93
+ const api = `${UI_URL}/systemview/api`;
73
94
  if (!(await appIsRunning(api))) {
74
95
  await launchApp(DEFAULT_PORT);
75
96
  }
76
97
 
77
98
  let connectedServices = [];
78
99
  if (namespace && project_code) {
79
- const manifestFile = flags.manifest || path.join(process.cwd(), "systemview.manifest.json");
80
- if (fs.existsSync(manifestFile)) {
81
- try {
82
- const raw = JSON.parse(fs.readFileSync(manifestFile, "utf8"));
83
- connectedServices = raw.services ? raw.services : [raw];
84
- } catch {}
85
- } else {
86
- try {
87
- const { SystemView } = await createCookieClient().loadService(api);
88
- connectedServices = (await SystemView.getServices(project_code)) || [];
89
- } catch {}
90
- }
100
+ try {
101
+ const { SystemView } = await Client.loadService(api);
102
+ connectedServices = (await SystemView.getServices(project_code)) || [];
103
+ } catch {}
91
104
  }
92
105
 
93
- openBrowser(ui, project_code, namespace, connectedServices);
106
+ openBrowser(UI_URL, project_code, namespace, connectedServices);
94
107
  process.exit(0);
95
108
  }
96
109
 
@@ -127,6 +140,8 @@ async function quitApp() {
127
140
  }
128
141
 
129
142
  init();
143
+ loadManifest();
144
+
130
145
  const command = input[0];
131
146
 
132
147
  if (command === "open") {
@@ -140,15 +155,54 @@ async function quitApp() {
140
155
  } else if (["exit", "q", "shutdown", "stop"].includes(command)) {
141
156
  await quitApp();
142
157
  } else if (command === "connect") {
143
- await connectService(input[1], input[2], { manifest: flags.manifest });
158
+ const useManifest = process.argv.includes("--manifest");
159
+ await connectService(input[1], {
160
+ useManifest,
161
+ force: flags.force,
162
+ connectedUrls,
163
+ uiUrl: UI_URL,
164
+ });
165
+ process.exit(0);
166
+ } else if (command === "disconnect") {
167
+ await manifestCommands.disconnect(input[1], input[2], {
168
+ connectedUrls,
169
+ uiUrl: UI_URL,
170
+ });
171
+ process.exit(0);
172
+ } else if (command === "manifest") {
173
+ const sub = input[1];
174
+ if (sub === "save") {
175
+ log.warn("manifest save requires an interactive session. Run: systemview start");
176
+ } else if (sub === "clean") {
177
+ await manifestCommands.clean(MANIFEST_FILE);
178
+ } else {
179
+ log.warn("Usage: systemview manifest <save|clean>");
180
+ }
144
181
  process.exit(0);
145
182
  } else if (command === "probe") {
183
+ await launchApp(DEFAULT_PORT);
146
184
  const exitCode = await probe(input[1], input[2], {
147
185
  json: flags.json,
148
186
  manifest: flags.manifest,
149
187
  headers: flags.headers,
188
+ uiUrl: UI_URL,
150
189
  });
151
190
  process.exit(exitCode || 0);
191
+ } else if (command === "logs") {
192
+ await launchApp(DEFAULT_PORT);
193
+ await logsCommand(input[1], input[2], {
194
+ uiUrl: UI_URL,
195
+ level: flags.level,
196
+ limit: flags.limit,
197
+ clear: flags.clear,
198
+ json: flags.json,
199
+ current: flags.current,
200
+ verbose: flags.verbose,
201
+ filter: flags.filter,
202
+ or: flags.or,
203
+ include: flags.include,
204
+ });
205
+ process.exit(0);
152
206
  } else {
153
207
  await startApp();
154
208
  }
package/cli/launchApp.js CHANGED
@@ -3,7 +3,7 @@ const appIsRunning = require("./appIsRunning");
3
3
  const launchSystemView = require("../api");
4
4
  const startLineReader = require("./startLineReader");
5
5
 
6
- module.exports = async function launchApp(port, { interactive = false } = {}) {
6
+ module.exports = async function launchApp(port, { interactive = false, connectedUrls = new Set() } = {}) {
7
7
  const ui = `http://localhost:${port}`;
8
8
  const api = `${ui}/systemview/api`;
9
9
 
@@ -15,17 +15,18 @@ module.exports = async function launchApp(port, { interactive = false } = {}) {
15
15
 
16
16
  if (await appIsRunning(api)) {
17
17
  if (interactive) {
18
- log.info("SystemView is running from another terminal");
18
+ log.info(`SystemView already running on port ${port} — attaching.`);
19
19
  logConnection();
20
+ return startLineReader(ui, { connectedUrls });
20
21
  }
21
22
  return;
22
23
  }
23
24
 
24
- if (interactive) log.info("Launching...");
25
+ if (interactive) log.info(`Launching on port ${port}...`);
25
26
  await launchSystemView(port);
26
27
  logConnection();
27
28
 
28
29
  if (interactive) {
29
- return startLineReader(ui);
30
+ return startLineReader(ui, { connectedUrls });
30
31
  }
31
32
  };
package/cli/listTests.js CHANGED
@@ -1,19 +1,20 @@
1
- const fs = require("fs");
2
- const path = require("path");
3
- const { createCookieClient } = require("./cookieClient");
1
+ const { createClient } = require("systemlynx");
2
+ const { createCookieHttpClient } = require("./cookieClient");
4
3
  const log = require("./logger");
5
4
  const chalk = require("chalk");
6
5
 
7
- module.exports = async function listTests(url, project_code, namespace, { manifest: manifestPath, verbose = false } = {}) {
6
+ const cookieHttpClient = createCookieHttpClient();
7
+ const Client = createClient(cookieHttpClient);
8
+
9
+ module.exports = async function listTests(url, project_code, namespace, { connectedUrls = new Set(), verbose = false } = {}) {
8
10
  const api = `${url}/systemview/api`;
9
- const Client = createCookieClient();
10
11
 
11
12
  if (!project_code) {
12
- await listAllProjects(api, Client, verbose);
13
+ await listAllProjects(api, Client, verbose, connectedUrls);
13
14
  return;
14
15
  }
15
16
 
16
- const connectedServices = await loadServices(api, project_code, manifestPath, Client);
17
+ const connectedServices = await loadServices(api, project_code, Client);
17
18
  if (!connectedServices.length) {
18
19
  log.warn("No connected services found for project: " + project_code);
19
20
  return;
@@ -30,7 +31,7 @@ module.exports = async function listTests(url, project_code, namespace, { manife
30
31
 
31
32
  let testList = [];
32
33
  try {
33
- const svc = createCookieClient().createService(system.connectionData);
34
+ const svc = Client.createService(system.connectionData);
34
35
  testList = await svc.Plugin.getTests();
35
36
  } catch {
36
37
  console.log(`${servicePrefix}${chalk.dim(serviceId)} ${chalk.dim("(could not load tests)")}`);
@@ -93,7 +94,7 @@ module.exports = async function listTests(url, project_code, namespace, { manife
93
94
  console.log("");
94
95
  };
95
96
 
96
- async function listAllProjects(api, Client, verbose = false) {
97
+ async function listAllProjects(api, Client, verbose = false, connectedUrls = new Set()) {
97
98
  let projects;
98
99
  try {
99
100
  const { SystemView } = await Client.loadService(api);
@@ -120,9 +121,11 @@ async function listAllProjects(api, Client, verbose = false) {
120
121
  const isLastService = si === services.length - 1;
121
122
  const servicePrefix = isLastService ? " └── " : " ├── ";
122
123
  const childPad = isLastService ? " " : " │ ";
124
+ const connected = connectedUrls.has(serviceUrl);
125
+ const connectedMark = connected ? chalk.green(" ●") : chalk.dim(" ○");
123
126
 
124
127
  if (verbose && connectionData && connectionData.modules) {
125
- console.log(`${servicePrefix}${chalk.bold(serviceId)} ${chalk.dim(serviceUrl)}`);
128
+ console.log(`${servicePrefix}${chalk.bold(serviceId)}${connectedMark} ${chalk.dim(serviceUrl)}`);
126
129
  const mods = connectionData.modules.filter((m) => m.name !== "Plugin");
127
130
  mods.forEach((mod, mi) => {
128
131
  const isLastMod = mi === mods.length - 1;
@@ -137,7 +140,7 @@ async function listAllProjects(api, Client, verbose = false) {
137
140
  });
138
141
  });
139
142
  } else {
140
- console.log(`${servicePrefix}${serviceId} ${chalk.dim(serviceUrl)}`);
143
+ console.log(`${servicePrefix}${serviceId}${connectedMark} ${chalk.dim(serviceUrl)}`);
141
144
  }
142
145
  });
143
146
  if (pi < codes.length - 1) console.log("");
@@ -145,14 +148,7 @@ async function listAllProjects(api, Client, verbose = false) {
145
148
  console.log("");
146
149
  }
147
150
 
148
- async function loadServices(api, project_code, manifestPath, Client) {
149
- const manifestFile = manifestPath || path.join(process.cwd(), "systemview.manifest.json");
150
- if (fs.existsSync(manifestFile)) {
151
- try {
152
- const raw = JSON.parse(fs.readFileSync(manifestFile, "utf8"));
153
- return raw.services ? raw.services : [raw];
154
- } catch {}
155
- }
151
+ async function loadServices(api, project_code, Client) {
156
152
  try {
157
153
  const { SystemView } = await Client.loadService(api);
158
154
  return (await SystemView.getServices(project_code)) || [];