@bizone-ai/cli 0.1.4 → 0.1.5

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,28 +1,28 @@
1
- import {BIZONE_SERVICE_ENV} from "./_commons.js";
1
+ import { BIZONE_SERVICE_ENV } from "./_commons.js";
2
2
 
3
3
  export const STATE_STORE_SERVICE = {
4
- type: 'state-store',
5
- image: `699453144787.dkr.ecr.us-east-1.amazonaws.com/state-store:latest`,
6
- containerName: 'state-store',
7
- containerPort: 80,
8
- portKey: 'state_store_port',
9
- defaultPort: '8088',
10
- enableKey: 'state_store_enable',
11
- readyPath: '/.system/status',
12
- readyCheck: 'systemStatusOk',
13
- defaultEnv: {
14
- "http.port": "80",
15
- "transport.config.endpoint": "http://bizone-res-manager/",
4
+ type: "state-store",
5
+ image: "699453144787.dkr.ecr.us-east-1.amazonaws.com/state-store:latest",
6
+ containerName: "state-store",
7
+ containerPort: 80,
8
+ portKey: "state_store_port",
9
+ defaultPort: "8088",
10
+ enableKey: "state_store_enable",
11
+ readyPath: "/.system/status",
12
+ readyCheck: "systemStatusOk",
13
+ defaultEnv: {
14
+ "http.port": "80",
15
+ "transport.config.endpoint": "http://bizone-res-manager/",
16
16
 
17
- 'storage.type': 'mysql',
18
- "storage.supported": "memory,mysql",
17
+ "storage.type": "mysql",
18
+ "storage.supported": "memory,mysql",
19
19
 
20
- 'mysql.storage': 'jdbc:mysql://bizone-mysql:3306',
21
- 'mysql.storage.user': 'root',
22
- 'mysql.storage.password.secret': 'mysql/password',
20
+ "mysql.storage": "jdbc:mysql://bizone-mysql:3306",
21
+ "mysql.storage.user": "root",
22
+ "mysql.storage.password.secret": "mysql/password",
23
23
 
24
- 'secret.mysql.password': 'root',
24
+ "secret.mysql.password": "root",
25
25
 
26
- ...BIZONE_SERVICE_ENV
27
- }
28
- };
26
+ ...BIZONE_SERVICE_ENV,
27
+ },
28
+ };
@@ -1,15 +1,15 @@
1
1
  export const UI_SERVICE = {
2
- type: 'ui',
3
- image: "699453144787.dkr.ecr.us-east-1.amazonaws.com/bizone-ui:latest",
4
- containerName: 'bizone-ui',
5
- containerPort: 7006,
6
- portKey: 'ui_port',
7
- defaultPort: '7006',
8
- enableKey: null,
9
- readyPath: '/actuator/health/readiness',
10
- readyCheck: 'uiReadinessUp',
11
- defaultEnv: {
12
- "resource-manager.url": "http://bizone-res-manager/",
13
- "auth.type": "none"
14
- }
15
- }
2
+ type: "ui",
3
+ image: "699453144787.dkr.ecr.us-east-1.amazonaws.com/bizone-ui:latest",
4
+ containerName: "bizone-ui",
5
+ containerPort: 7006,
6
+ portKey: "ui_port",
7
+ defaultPort: "7006",
8
+ enableKey: null,
9
+ readyPath: "/actuator/health/readiness",
10
+ readyCheck: "uiReadinessUp",
11
+ defaultEnv: {
12
+ "resource-manager.url": "http://bizone-res-manager/",
13
+ "auth.type": "none",
14
+ },
15
+ };
package/src/defaults.js CHANGED
@@ -1,21 +1,21 @@
1
1
  // Static defaults and platform topology for the Bizone CLI.
2
2
 
3
- import {ORCHESTRATOR_SERVICE} from "./containers/orchestrator.js";
4
- import {RESOURCE_MANAGER_SERVICE} from "./containers/resource-manager.js";
5
- import {STATE_STORE_SERVICE} from "./containers/state-store.js";
6
- import {MYSQL_SERVICE} from "./containers/mysql.js";
7
- import {CLOUD_TOOLS_SERVICE} from "./containers/cloud-tools.js";
8
- import {UI_SERVICE} from "./containers/ui.js";
9
- import {RESOURCES_JOB} from "./containers/resources-job.js";
3
+ import { ORCHESTRATOR_SERVICE } from "./containers/orchestrator.js";
4
+ import { RESOURCE_MANAGER_SERVICE } from "./containers/resource-manager.js";
5
+ import { STATE_STORE_SERVICE } from "./containers/state-store.js";
6
+ import { MYSQL_SERVICE } from "./containers/mysql.js";
7
+ import { CLOUD_TOOLS_SERVICE } from "./containers/cloud-tools.js";
8
+ import { UI_SERVICE } from "./containers/ui.js";
9
+ import { RESOURCES_JOB } from "./containers/resources-job.js";
10
10
 
11
11
  export const CONTAINERS = {
12
12
  mysql: MYSQL_SERVICE,
13
- 'resource-manager': RESOURCE_MANAGER_SERVICE,
13
+ "resource-manager": RESOURCE_MANAGER_SERVICE,
14
14
  orchestrator: ORCHESTRATOR_SERVICE,
15
- 'state-store': STATE_STORE_SERVICE,
16
- 'cloud-tools': CLOUD_TOOLS_SERVICE,
15
+ "state-store": STATE_STORE_SERVICE,
16
+ "cloud-tools": CLOUD_TOOLS_SERVICE,
17
17
  ui: UI_SERVICE,
18
- resources: RESOURCES_JOB
18
+ resources: RESOURCES_JOB,
19
19
  };
20
20
 
21
21
  // Valid image / environment "types" (a.k.a. container types).
@@ -26,39 +26,47 @@ export function defaultEnvFor(type) {
26
26
  }
27
27
 
28
28
  // Shared docker network name (default; overridable via the `network_name` config key).
29
- export const NETWORK_NAME = 'bizone-local';
29
+ export const NETWORK_NAME = "bizone-local";
30
30
 
31
31
  // Defaults for general `config` keys.
32
32
  export const DEFAULT_CONFIG = {
33
- forward_aws_env: 'true',
33
+ forward_aws_env: "true",
34
+ aws_profile: "",
34
35
  network_name: NETWORK_NAME,
35
36
  mysql_port: CONTAINERS.mysql.defaultPort,
36
37
  orchestrator_port: CONTAINERS.orchestrator.defaultPort,
37
38
  resource_manager_port: CONTAINERS["resource-manager"].defaultPort,
38
39
  state_store_port: CONTAINERS["state-store"].defaultPort,
39
- state_store_enable: 'true',
40
+ state_store_enable: "true",
40
41
  cloud_tools_port: CONTAINERS["cloud-tools"].defaultPort,
41
- cloud_tools_enable: 'false',
42
+ cloud_tools_enable: "false",
42
43
  ui_port: CONTAINERS.ui.defaultPort,
43
- timeout_sec: '300',
44
+ timeout_sec: "300",
44
45
  };
45
46
 
46
47
  // Known config keys (used for validation / help).
47
48
  export const CONFIG_KEYS = Object.keys(DEFAULT_CONFIG);
48
49
 
49
50
  // AWS env vars forwarded into the orchestrator runtime when forward_aws_env=true.
50
- export const AWS_ENV_VARS = ['AWS_REGION', 'AWS_ACCESS_KEY_ID', 'AWS_SECRET_ACCESS_KEY'];
51
+ export const AWS_ENV_VARS = [
52
+ "AWS_REGION",
53
+ "AWS_ACCESS_KEY_ID",
54
+ "AWS_SECRET_ACCESS_KEY",
55
+ ];
51
56
 
52
57
  // Predefined secret env-var names. When showing env (`environment get`),
53
58
  // values for these keys are masked. Matching is case-insensitive and also
54
59
  // triggers on any key containing one of the SECRET_KEY_SUBSTRINGS below.
55
- export const SECRET_ENV_KEYS = [
56
- 'AWS_SECRET_ACCESS_KEY',
57
- 'AWS_ACCESS_KEY_ID',
58
- ];
60
+ export const SECRET_ENV_KEYS = ["AWS_SECRET_ACCESS_KEY", "AWS_ACCESS_KEY_ID"];
59
61
 
60
- export const SECRET_KEY_SUBSTRINGS = ['password', 'secret', 'token', 'apikey', 'api_key'];
62
+ export const SECRET_KEY_SUBSTRINGS = [
63
+ "password",
64
+ "secret",
65
+ "token",
66
+ "apikey",
67
+ "api_key",
68
+ ];
61
69
 
62
70
  // Importing procedure constants.
63
- export const IMPORT_USER = 'bizone-cli';
64
- export const IMPORT_MESSAGE = 'resource initialization';
71
+ export const IMPORT_USER = "bizone-cli";
72
+ export const IMPORT_MESSAGE = "resource initialization";
package/src/docker.js CHANGED
@@ -1,9 +1,9 @@
1
1
  // Docker helpers. All commands shell out to the local `docker` binary.
2
2
 
3
- import { spawnSync, spawn } from 'node:child_process';
3
+ import { spawnSync, spawn } from "node:child_process";
4
4
 
5
5
  function run(args, opts = {}) {
6
- return spawnSync('docker', args, { encoding: 'utf8', ...opts });
6
+ return spawnSync("docker", args, { encoding: "utf8", ...opts });
7
7
  }
8
8
 
9
9
  // --- AWS ECR auto-login ---------------------------------------------------
@@ -28,27 +28,35 @@ function parseEcr(image) {
28
28
 
29
29
  // Authenticate docker to the ECR registry backing `image`, using the AWS CLI.
30
30
  // No-op (returns false) for non-ECR images. Throws on failure.
31
- function ecrLogin(ecr) {
31
+ function ecrLogin(ecr, profile) {
32
32
  if (!ecr) return false;
33
33
  if (loggedInRegistries.has(ecr.registry)) return true;
34
34
 
35
- const pw = spawnSync('aws', ['ecr', 'get-login-password', '--region', ecr.region], {
36
- encoding: 'utf8',
37
- });
35
+ const awsArgs = ["ecr", "get-login-password", "--region", ecr.region];
36
+ if (profile) awsArgs.push("--profile", profile);
37
+
38
+ const pw = spawnSync("aws", awsArgs, { encoding: "utf8" });
38
39
  if (pw.status !== 0) {
40
+ const profileNote = profile ? ` --profile ${profile}` : "";
39
41
  throw new Error(
40
- `Failed to obtain ECR password (aws ecr get-login-password --region ${ecr.region}): ` +
41
- `${(pw.stderr || pw.error?.message || '').trim()}. Is the AWS CLI installed and configured?`,
42
+ `Failed to obtain ECR password (aws ecr get-login-password --region ${ecr.region}${profileNote}): ` +
43
+ `${(
44
+ pw.stderr ||
45
+ pw.error?.message ||
46
+ ""
47
+ ).trim()}. Is the AWS CLI installed and configured?`,
42
48
  );
43
49
  }
44
50
 
45
51
  const login = spawnSync(
46
- 'docker',
47
- ['login', '--username', 'AWS', '--password-stdin', ecr.registry],
48
- { input: pw.stdout.trim(), encoding: 'utf8' },
52
+ "docker",
53
+ ["login", "--username", "AWS", "--password-stdin", ecr.registry],
54
+ { input: pw.stdout.trim(), encoding: "utf8" },
49
55
  );
50
56
  if (login.status !== 0) {
51
- throw new Error(`docker login to ${ecr.registry} failed: ${(login.stderr || '').trim()}`);
57
+ throw new Error(
58
+ `docker login to ${ecr.registry} failed: ${(login.stderr || "").trim()}`,
59
+ );
52
60
  }
53
61
 
54
62
  loggedInRegistries.add(ecr.registry);
@@ -59,62 +67,97 @@ function ecrLogin(ecr) {
59
67
  // against an ECR image, logs in and retries once. Non-auth pull failures are
60
68
  // ignored here (the image may already exist locally) and surfaced later by the
61
69
  // actual `docker run`.
62
- export function ensureImage(image, { onBeforePull, onBeforeLogin } = {}) {
70
+ export function ensureImage(
71
+ image,
72
+ { onBeforePull, onBeforeLogin, awsProfile } = {},
73
+ ) {
63
74
  if (onBeforePull) onBeforePull(image);
64
- let r = run(['pull', image]);
75
+ let r = run(["pull", image]);
65
76
  if (r.status === 0) return;
66
77
 
67
- const out = `${r.stdout || ''}${r.stderr || ''}`;
78
+ const out = `${r.stdout || ""}${r.stderr || ""}`;
68
79
  if (AUTH_ERR_RE.test(out)) {
69
80
  const ecr = parseEcr(image);
70
81
  if (ecr) {
71
82
  if (onBeforeLogin) onBeforeLogin(ecr.registry);
72
- ecrLogin(ecr);
83
+ ecrLogin(ecr, awsProfile);
73
84
  if (onBeforePull) onBeforePull(image);
74
- r = run(['pull', image]);
85
+ r = run(["pull", image]);
75
86
  if (r.status !== 0) {
76
- throw new Error(`docker pull ${image} failed after ECR login: ${(r.stderr || '').trim()}`);
87
+ throw new Error(
88
+ `docker pull ${image} failed after ECR login: ${(
89
+ r.stderr || ""
90
+ ).trim()}`,
91
+ );
77
92
  }
78
93
  }
79
94
  }
80
95
  }
81
96
 
82
97
  export function dockerAvailable() {
83
- const r = run(['version', '--format', '{{.Server.Version}}']);
98
+ const r = run(["version", "--format", "{{.Server.Version}}"]);
84
99
  return r.status === 0;
85
100
  }
86
101
 
87
102
  // Create the shared network if it does not already exist. Never fails if present.
88
103
  export function ensureNetwork(network) {
89
- const ls = run(['network', 'ls', '--filter', `name=^${network}$`, '--format', '{{.Name}}']);
90
- if (ls.status === 0 && ls.stdout.split('\n').includes(network)) {
104
+ const ls = run([
105
+ "network",
106
+ "ls",
107
+ "--filter",
108
+ `name=^${network}$`,
109
+ "--format",
110
+ "{{.Name}}",
111
+ ]);
112
+ if (ls.status === 0 && ls.stdout.split("\n").includes(network)) {
91
113
  return false; // already existed
92
114
  }
93
- const create = run(['network', 'create', network]);
94
- if (create.status !== 0 && !/already exists/i.test(create.stderr || '')) {
95
- throw new Error(`Failed to create network ${network}: ${create.stderr.trim()}`);
115
+ const create = run(["network", "create", network]);
116
+ if (create.status !== 0 && !/already exists/i.test(create.stderr || "")) {
117
+ throw new Error(
118
+ `Failed to create network ${network}: ${create.stderr.trim()}`,
119
+ );
96
120
  }
97
121
  return true; // created
98
122
  }
99
123
 
100
124
  // Run a container detached. Returns the full container id.
101
- export function runDetached({ name, hostPort, containerPort, image, env = {}, volume = null, network }) {
102
- const args = ['run', '-d', '--name', name, '-h', name, '--network', network, '--network-alias', name];
125
+ export function runDetached({
126
+ name,
127
+ hostPort,
128
+ containerPort,
129
+ image,
130
+ env = {},
131
+ volume = null,
132
+ network,
133
+ }) {
134
+ const args = [
135
+ "run",
136
+ "-d",
137
+ "--name",
138
+ name,
139
+ "-h",
140
+ name,
141
+ "--network",
142
+ network,
143
+ "--network-alias",
144
+ name,
145
+ ];
103
146
  if (hostPort != null && containerPort != null) {
104
- args.push('-p', `${hostPort}:${containerPort}`);
147
+ args.push("-p", `${hostPort}:${containerPort}`);
105
148
  }
106
149
  if (volume && volume.name && volume.mount) {
107
- args.push('-v', `${volume.name}:${volume.mount}`);
150
+ args.push("-v", `${volume.name}:${volume.mount}`);
108
151
  }
109
152
  for (const [k, v] of Object.entries(env)) {
110
- if (v === undefined || v === null || v === '') continue;
111
- args.push('-e', `${k}=${v}`);
153
+ if (v === undefined || v === null || v === "") continue;
154
+ args.push("-e", `${k}=${v}`);
112
155
  }
113
156
  args.push(image);
114
157
 
115
158
  const r = run(args);
116
159
  if (r.status !== 0) {
117
- throw new Error(`docker run ${name} failed: ${(r.stderr || '').trim()}`);
160
+ throw new Error(`docker run ${name} failed: ${(r.stderr || "").trim()}`);
118
161
  }
119
162
  return r.stdout.trim();
120
163
  }
@@ -122,34 +165,47 @@ export function runDetached({ name, hostPort, containerPort, image, env = {}, vo
122
165
  // Run a container in the foreground (blocking), removing it on exit.
123
166
  export function runBlocking({ name, image, env = {}, network }) {
124
167
  return new Promise((resolve, reject) => {
125
- const args = ['run', '--rm', '--name', name, '--network', network];
168
+ const args = ["run", "--rm", "--name", name, "--network", network];
126
169
  for (const [k, v] of Object.entries(env)) {
127
- if (v === undefined || v === null || v === '') continue;
128
- args.push('-e', `${k}=${v}`);
170
+ if (v === undefined || v === null || v === "") continue;
171
+ args.push("-e", `${k}=${v}`);
129
172
  }
130
173
  args.push(image);
131
174
 
132
- const child = spawn('docker', args, { stdio: 'inherit' });
133
- child.on('error', reject);
134
- child.on('close', (code) => {
175
+ const child = spawn("docker", args, { stdio: "inherit" });
176
+ child.on("error", reject);
177
+ child.on("close", (code) => {
135
178
  if (code === 0) resolve();
136
- else reject(new Error(`Resources init job (${name}) exited with code ${code}`));
179
+ else
180
+ reject(
181
+ new Error(`Resources init job (${name}) exited with code ${code}`),
182
+ );
137
183
  });
138
184
  });
139
185
  }
140
186
 
141
187
  // Return the set of running container ids (full ids).
142
188
  export function runningContainerIds() {
143
- const r = run(['ps', '--no-trunc', '--format', '{{.ID}}']);
189
+ const r = run(["ps", "--no-trunc", "--format", "{{.ID}}"]);
144
190
  if (r.status !== 0) return new Set();
145
- return new Set(r.stdout.split('\n').map((s) => s.trim()).filter(Boolean));
191
+ return new Set(
192
+ r.stdout
193
+ .split("\n")
194
+ .map((s) => s.trim())
195
+ .filter(Boolean),
196
+ );
146
197
  }
147
198
 
148
199
  // Return the set of running container names.
149
200
  export function runningContainerNames() {
150
- const r = run(['ps', '--format', '{{.Names}}']);
201
+ const r = run(["ps", "--format", "{{.Names}}"]);
151
202
  if (r.status !== 0) return new Set();
152
- return new Set(r.stdout.split('\n').map((s) => s.trim()).filter(Boolean));
203
+ return new Set(
204
+ r.stdout
205
+ .split("\n")
206
+ .map((s) => s.trim())
207
+ .filter(Boolean),
208
+ );
153
209
  }
154
210
 
155
211
  export function isContainerRunning(name) {
@@ -158,33 +214,36 @@ export function isContainerRunning(name) {
158
214
 
159
215
  // Remove a named docker volume. Returns true on success.
160
216
  export function removeVolume(name) {
161
- return run(['volume', 'rm', name]).status === 0;
217
+ return run(["volume", "rm", name]).status === 0;
162
218
  }
163
219
 
164
220
  // Stop & remove a container by id or name. Ignores "no such container".
165
221
  export function stopContainer(idOrName) {
166
- const stop = run(['stop', idOrName]);
167
- run(['rm', '-f', idOrName]); // best-effort cleanup
222
+ const stop = run(["stop", idOrName]);
223
+ run(["rm", "-f", idOrName]); // best-effort cleanup
168
224
  return stop.status === 0;
169
225
  }
170
226
 
171
227
  // Remove a container if it already exists (cleanup before re-running).
172
228
  export function removeIfExists(name) {
173
- run(['rm', '-f', name]);
229
+ run(["rm", "-f", name]);
174
230
  }
175
231
 
176
232
  // Whether the MySQL server inside a container accepts connections.
177
- export function mysqlReady(containerName, { user = 'root', password = 'root' } = {}) {
233
+ export function mysqlReady(
234
+ containerName,
235
+ { user = "root", password = "root" } = {},
236
+ ) {
178
237
  const r = run([
179
- 'exec',
238
+ "exec",
180
239
  containerName,
181
- 'mysqladmin',
182
- 'ping',
183
- '-h',
184
- '127.0.0.1',
240
+ "mysqladmin",
241
+ "ping",
242
+ "-h",
243
+ "127.0.0.1",
185
244
  `-u${user}`,
186
245
  `-p${password}`,
187
- '--silent',
246
+ "--silent",
188
247
  ]);
189
248
  return r.status === 0;
190
249
  }
package/src/http.js CHANGED
@@ -1,18 +1,18 @@
1
1
  // HTTP helpers: generic readiness waiting + resource import.
2
2
 
3
- import { IMPORT_USER, IMPORT_MESSAGE } from './defaults.js';
3
+ import { IMPORT_USER, IMPORT_MESSAGE } from "./defaults.js";
4
4
 
5
5
  const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
6
6
 
7
7
  // Readiness predicates keyed by name (see SERVICES[].readyCheck).
8
8
  const CHECKS = {
9
- systemStatusOk: (body) => body && body.status === 'OK',
10
- uiReadinessUp: (body) => body && body.status && body.status.code === 'UP',
9
+ systemStatusOk: (body) => body && body.status === "OK",
10
+ uiReadinessUp: (body) => body && body.status && body.status.code === "UP",
11
11
  };
12
12
 
13
13
  async function probe(url) {
14
14
  try {
15
- const res = await fetch(url, { method: 'GET' });
15
+ const res = await fetch(url, { method: "GET" });
16
16
  if (!res.ok) return null;
17
17
  const text = await res.text();
18
18
  try {
@@ -27,7 +27,13 @@ async function probe(url) {
27
27
 
28
28
  // Generic wait-for-ready. Polls `url`, applying the named check, until it
29
29
  // passes or the timeout elapses. Returns true on success, throws on timeout.
30
- export async function waitForReady({ url, checkName, timeoutSec, label, onTick }) {
30
+ export async function waitForReady({
31
+ url,
32
+ checkName,
33
+ timeoutSec,
34
+ label,
35
+ onTick,
36
+ }) {
31
37
  const check = CHECKS[checkName];
32
38
  if (!check) throw new Error(`Unknown readiness check: ${checkName}`);
33
39
 
@@ -40,7 +46,9 @@ export async function waitForReady({ url, checkName, timeoutSec, label, onTick }
40
46
  if (onTick) onTick(attempt);
41
47
  await sleep(1500);
42
48
  }
43
- throw new Error(`Timed out waiting for ${label || url} to become ready (${timeoutSec}s)`);
49
+ throw new Error(
50
+ `Timed out waiting for ${label || url} to become ready (${timeoutSec}s)`,
51
+ );
44
52
  }
45
53
 
46
54
  // Import a set of items into a resource-manager namespace.
@@ -48,13 +56,15 @@ export async function importNamespace({ port, namespace, items }) {
48
56
  const url = `http://localhost:${port}/admin/${namespace}/status`;
49
57
  const payload = { user: IMPORT_USER, message: IMPORT_MESSAGE, items };
50
58
  const res = await fetch(url, {
51
- method: 'POST',
52
- headers: { 'content-type': 'application/json' },
59
+ method: "POST",
60
+ headers: { "content-type": "application/json" },
53
61
  body: JSON.stringify(payload),
54
62
  });
55
63
  if (!res.ok) {
56
- const body = await res.text().catch(() => '');
57
- throw new Error(`Import of namespace "${namespace}" failed: HTTP ${res.status} ${body}`);
64
+ const body = await res.text().catch(() => "");
65
+ throw new Error(
66
+ `Import of namespace "${namespace}" failed: HTTP ${res.status} ${body}`,
67
+ );
58
68
  }
59
69
  return true;
60
70
  }
@@ -5,13 +5,13 @@
5
5
  // - single file with an "items" field: use that array
6
6
  // - single file otherwise: the file contents become the sole item
7
7
 
8
- import { readFileSync, statSync, readdirSync } from 'node:fs';
9
- import { join, isAbsolute, resolve } from 'node:path';
8
+ import { readFileSync, statSync, readdirSync } from "node:fs";
9
+ import { join, isAbsolute, resolve } from "node:path";
10
10
 
11
- import { BIZONE_DIR } from './config.js';
11
+ import { BIZONE_DIR } from "./config.js";
12
12
 
13
13
  function readJson(path) {
14
- const raw = readFileSync(path, 'utf8');
14
+ const raw = readFileSync(path, "utf8");
15
15
  return JSON.parse(raw);
16
16
  }
17
17
 
@@ -20,7 +20,7 @@ function collectJsonFiles(dir, out = []) {
20
20
  const full = join(dir, entry.name);
21
21
  if (entry.isDirectory()) {
22
22
  collectJsonFiles(full, out);
23
- } else if (entry.isFile() && entry.name.toLowerCase().endsWith('.json')) {
23
+ } else if (entry.isFile() && entry.name.toLowerCase().endsWith(".json")) {
24
24
  out.push(full);
25
25
  }
26
26
  }
@@ -43,7 +43,7 @@ export function loadItems(configuredPath) {
43
43
  }
44
44
 
45
45
  const content = readJson(path);
46
- if (content && typeof content === 'object' && Array.isArray(content.items)) {
46
+ if (content && typeof content === "object" && Array.isArray(content.items)) {
47
47
  return content.items;
48
48
  }
49
49
  return [content];