@ibm/ixora 0.1.3 → 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.
@@ -6,7 +6,7 @@ import {
6
6
  IXORA_DIR,
7
7
  SYSTEMS_CONFIG,
8
8
  readSystems
9
- } from "./chunk-VCQRSQ4F.js";
9
+ } from "./chunk-KFC4SP2O.js";
10
10
 
11
11
  // src/lib/compose.ts
12
12
  import { execa as execa2 } from "execa";
@@ -250,6 +250,7 @@ function section(title) {
250
250
 
251
251
  // src/lib/compose.ts
252
252
  async function runCompose(composeCmd, args, options = {}) {
253
+ const { throwOnError, ...execaOpts } = options;
253
254
  const [bin, subArgs] = getComposeParts(composeCmd);
254
255
  const fullArgs = [
255
256
  ...subArgs,
@@ -264,7 +265,7 @@ async function runCompose(composeCmd, args, options = {}) {
264
265
  try {
265
266
  const result = await execa2(bin, fullArgs, {
266
267
  stdio: "inherit",
267
- ...options
268
+ ...execaOpts
268
269
  });
269
270
  return {
270
271
  stdout: String(result.stdout ?? ""),
@@ -273,6 +274,11 @@ async function runCompose(composeCmd, args, options = {}) {
273
274
  };
274
275
  } catch (err) {
275
276
  const exitCode = err && typeof err === "object" && "exitCode" in err ? err.exitCode : 1;
277
+ if (throwOnError) {
278
+ throw new Error(
279
+ `Compose command failed (exit ${exitCode}): ${args.join(" ")}`
280
+ );
281
+ }
276
282
  error(`Command failed: ${composeCmd} ${args.join(" ")}`);
277
283
  console.log(` Check ${bold("ixora logs")} for details.`);
278
284
  process.exit(exitCode);
@@ -2,18 +2,36 @@
2
2
 
3
3
  // src/lib/systems.ts
4
4
  import {
5
- readFileSync as readFileSync2,
5
+ readFileSync as readFileSync3,
6
6
  writeFileSync as writeFileSync2,
7
7
  existsSync as existsSync2,
8
8
  mkdirSync as mkdirSync2,
9
9
  chmodSync as chmodSync2
10
10
  } from "fs";
11
- import { dirname as dirname2 } from "path";
11
+ import { dirname as dirname3 } from "path";
12
12
 
13
13
  // src/lib/constants.ts
14
+ import { readFileSync } from "fs";
14
15
  import { homedir } from "os";
15
- import { join } from "path";
16
- var SCRIPT_VERSION = "0.1.3";
16
+ import { dirname, join } from "path";
17
+ import { fileURLToPath } from "url";
18
+ function readCliVersion() {
19
+ let dir = dirname(fileURLToPath(import.meta.url));
20
+ for (let i = 0; i < 5; i++) {
21
+ try {
22
+ const pkg = JSON.parse(
23
+ readFileSync(join(dir, "package.json"), "utf8")
24
+ );
25
+ if (pkg.name === "@ibm/ixora" && pkg.version) return pkg.version;
26
+ } catch {
27
+ }
28
+ const parent = dirname(dir);
29
+ if (parent === dir) break;
30
+ dir = parent;
31
+ }
32
+ return "unknown";
33
+ }
34
+ var SCRIPT_VERSION = readCliVersion();
17
35
  var HEALTH_TIMEOUT = 30;
18
36
  var IXORA_DIR = join(homedir(), ".ixora");
19
37
  var COMPOSE_FILE = join(IXORA_DIR, "docker-compose.yml");
@@ -112,19 +130,19 @@ var AGENT_PRESETS = {
112
130
 
113
131
  // src/lib/env.ts
114
132
  import {
115
- readFileSync,
133
+ readFileSync as readFileSync2,
116
134
  writeFileSync,
117
135
  existsSync,
118
136
  mkdirSync,
119
137
  chmodSync
120
138
  } from "fs";
121
- import { dirname } from "path";
139
+ import { dirname as dirname2 } from "path";
122
140
  function sqEscape(value) {
123
141
  return value.replace(/'/g, "'\\''");
124
142
  }
125
143
  function envGet(key, envFile = ENV_FILE) {
126
144
  if (!existsSync(envFile)) return "";
127
- const content = readFileSync(envFile, "utf-8");
145
+ const content = readFileSync2(envFile, "utf-8");
128
146
  for (const line of content.split("\n")) {
129
147
  if (line.startsWith(`${key}=`)) {
130
148
  let val = line.slice(key.length + 1);
@@ -147,21 +165,28 @@ var KNOWN_KEYS = [
147
165
  "DB2_PORT",
148
166
  "IXORA_PROFILE",
149
167
  "IXORA_VERSION",
168
+ "IXORA_PREVIOUS_VERSION",
150
169
  "IXORA_AGENT_MODEL",
151
170
  "IXORA_TEAM_MODEL"
152
171
  ];
153
172
  function writeEnvFile(config, envFile = ENV_FILE) {
154
- mkdirSync(dirname(envFile), { recursive: true });
173
+ mkdirSync(dirname2(envFile), { recursive: true });
155
174
  let extra = "";
175
+ let prevVersionLine = "";
156
176
  if (existsSync(envFile)) {
157
- const existing = readFileSync(envFile, "utf-8");
158
- const extraLines = existing.split("\n").filter((line) => {
177
+ const existing = readFileSync2(envFile, "utf-8");
178
+ const lines = existing.split("\n");
179
+ const extraLines = lines.filter((line) => {
159
180
  const trimmed = line.trim();
160
181
  if (!trimmed || trimmed.startsWith("#")) return false;
161
182
  const lineKey = trimmed.split("=")[0];
162
183
  return !KNOWN_KEYS.includes(lineKey);
163
184
  });
164
185
  extra = extraLines.join("\n");
186
+ const pvLine = lines.find(
187
+ (l) => l.startsWith("IXORA_PREVIOUS_VERSION=")
188
+ );
189
+ if (pvLine) prevVersionLine = pvLine;
165
190
  }
166
191
  let content = `# Model provider
167
192
  IXORA_AGENT_MODEL='${sqEscape(config.agentModel)}'
@@ -186,6 +211,10 @@ DB2_PORT='${sqEscape(config.db2Port)}'
186
211
  IXORA_PROFILE='${sqEscape(config.profile)}'
187
212
  IXORA_VERSION='${sqEscape(config.version)}'
188
213
  `;
214
+ if (prevVersionLine) {
215
+ content += `${prevVersionLine}
216
+ `;
217
+ }
189
218
  if (extra) {
190
219
  content += `
191
220
  # Preserved user settings
@@ -198,7 +227,7 @@ ${extra}
198
227
  function updateEnvKey(key, value, envFile = ENV_FILE) {
199
228
  const escaped = sqEscape(value);
200
229
  if (existsSync(envFile)) {
201
- const content = readFileSync(envFile, "utf-8");
230
+ const content = readFileSync2(envFile, "utf-8");
202
231
  const lines = content.split("\n");
203
232
  let found = false;
204
233
  const updated = lines.map((line) => {
@@ -211,7 +240,7 @@ function updateEnvKey(key, value, envFile = ENV_FILE) {
211
240
  if (found) {
212
241
  writeFileSync(envFile, updated.join("\n"), "utf-8");
213
242
  } else {
214
- const existing = readFileSync(envFile, "utf-8");
243
+ const existing = readFileSync2(envFile, "utf-8");
215
244
  const suffix = existing.endsWith("\n") ? "" : "\n";
216
245
  writeFileSync(
217
246
  envFile,
@@ -221,7 +250,7 @@ function updateEnvKey(key, value, envFile = ENV_FILE) {
221
250
  );
222
251
  }
223
252
  } else {
224
- mkdirSync(dirname(envFile), { recursive: true });
253
+ mkdirSync(dirname2(envFile), { recursive: true });
225
254
  writeFileSync(envFile, `${key}='${escaped}'
226
255
  `, "utf-8");
227
256
  }
@@ -231,7 +260,7 @@ function updateEnvKey(key, value, envFile = ENV_FILE) {
231
260
  // src/lib/systems.ts
232
261
  function readSystems(configFile = SYSTEMS_CONFIG) {
233
262
  if (!existsSync2(configFile)) return [];
234
- const content = readFileSync2(configFile, "utf-8");
263
+ const content = readFileSync3(configFile, "utf-8");
235
264
  const systems = [];
236
265
  let current = null;
237
266
  for (const line of content.split("\n")) {
@@ -297,7 +326,7 @@ function addSystem(system, envFile = ENV_FILE, configFile = SYSTEMS_CONFIG) {
297
326
  profile: ${profile}
298
327
  agents: [${agentsList}]
299
328
  `;
300
- mkdirSync2(dirname2(configFile), { recursive: true });
329
+ mkdirSync2(dirname3(configFile), { recursive: true });
301
330
  if (!existsSync2(configFile) || systemCount(configFile) === 0) {
302
331
  const content = `# yaml-language-server: $schema=
303
332
  # Ixora Systems Configuration
@@ -307,7 +336,7 @@ systems:
307
336
  ${entry}`;
308
337
  writeFileSync2(configFile, content, "utf-8");
309
338
  } else {
310
- const existing = readFileSync2(configFile, "utf-8");
339
+ const existing = readFileSync3(configFile, "utf-8");
311
340
  writeFileSync2(configFile, `${existing}${entry}`, "utf-8");
312
341
  }
313
342
  chmodSync2(configFile, 384);
@@ -319,7 +348,7 @@ function removeSystem(id, envFile = ENV_FILE, configFile = SYSTEMS_CONFIG) {
319
348
  if (!systemIdExists(id, configFile)) {
320
349
  throw new Error(`System '${id}' not found`);
321
350
  }
322
- const content = readFileSync2(configFile, "utf-8");
351
+ const content = readFileSync3(configFile, "utf-8");
323
352
  const lines = content.split("\n");
324
353
  const output = [];
325
354
  let skip = false;
@@ -339,7 +368,7 @@ function removeSystem(id, envFile = ENV_FILE, configFile = SYSTEMS_CONFIG) {
339
368
  chmodSync2(configFile, 384);
340
369
  if (existsSync2(envFile)) {
341
370
  const idUpper = id.toUpperCase().replace(/-/g, "_");
342
- const envContent = readFileSync2(envFile, "utf-8");
371
+ const envContent = readFileSync3(envFile, "utf-8");
343
372
  const filtered = envContent.split("\n").filter((line) => !line.startsWith(`SYSTEM_${idUpper}_`)).join("\n");
344
373
  writeFileSync2(envFile, filtered, "utf-8");
345
374
  chmodSync2(envFile, 384);
package/dist/index.js CHANGED
@@ -22,7 +22,7 @@ import {
22
22
  waitForHealthy,
23
23
  warn,
24
24
  writeComposeFile
25
- } from "./chunk-4XGJZZ73.js";
25
+ } from "./chunk-K2GF2QLM.js";
26
26
  import {
27
27
  COMPOSE_FILE,
28
28
  ENV_FILE,
@@ -39,7 +39,7 @@ import {
39
39
  systemIdExists,
40
40
  updateEnvKey,
41
41
  writeEnvFile
42
- } from "./chunk-VCQRSQ4F.js";
42
+ } from "./chunk-KFC4SP2O.js";
43
43
 
44
44
  // src/cli.ts
45
45
  import { Command } from "commander";
@@ -297,6 +297,12 @@ function normalizeVersion(version) {
297
297
  }
298
298
 
299
299
  // src/commands/upgrade.ts
300
+ function rollback(previousVersion) {
301
+ warn("Rolling back to previous version...");
302
+ updateEnvKey("IXORA_VERSION", previousVersion);
303
+ writeComposeFile();
304
+ info(`Reverted IXORA_VERSION to ${previousVersion}`);
305
+ }
300
306
  async function cmdUpgrade(opts) {
301
307
  try {
302
308
  requireInstalled();
@@ -335,7 +341,8 @@ async function cmdUpgrade(opts) {
335
341
  }))
336
342
  });
337
343
  }
338
- info(`Upgrading ixora: ${previousVersion} \u2192 ${targetVersion}`);
344
+ info(`Upgrading ixora: ${previousVersion} -> ${targetVersion}`);
345
+ updateEnvKey("IXORA_PREVIOUS_VERSION", previousVersion);
339
346
  info("Stopping services...");
340
347
  await runCompose(composeCmd, ["down", "--remove-orphans"]);
341
348
  updateEnvKey("IXORA_VERSION", targetVersion);
@@ -350,13 +357,31 @@ async function cmdUpgrade(opts) {
350
357
  info(`Setting profile: ${opts.profile}`);
351
358
  updateEnvKey("IXORA_PROFILE", opts.profile);
352
359
  }
353
- if (opts.pull !== false) {
354
- info("Pulling images...");
355
- await runCompose(composeCmd, ["pull"]);
360
+ try {
361
+ if (opts.pull !== false) {
362
+ info("Pulling images...");
363
+ await runCompose(composeCmd, ["pull"], { throwOnError: true });
364
+ }
365
+ info("Starting services...");
366
+ await runCompose(composeCmd, ["up", "-d"], { throwOnError: true });
367
+ const healthy = await waitForHealthy(composeCmd);
368
+ if (!healthy) {
369
+ throw new Error(
370
+ "Services did not become healthy after upgrade"
371
+ );
372
+ }
373
+ } catch (err) {
374
+ rollback(previousVersion);
375
+ try {
376
+ await runCompose(composeCmd, ["down", "--remove-orphans"]);
377
+ } catch {
378
+ }
379
+ error(err.message);
380
+ info(
381
+ `Run ${bold("ixora logs")} to investigate, then retry with ${bold(`ixora upgrade ${targetVersion}`)}`
382
+ );
383
+ process.exit(1);
356
384
  }
357
- info("Restarting services...");
358
- await runCompose(composeCmd, ["up", "-d"]);
359
- await waitForHealthy(composeCmd);
360
385
  const profile = envGet("IXORA_PROFILE") || "full";
361
386
  console.log();
362
387
  success("Upgrade complete!");
@@ -703,7 +728,7 @@ async function cmdInstall(opts) {
703
728
  writeEnvFile(envConfig);
704
729
  success("Wrote .env");
705
730
  if (systemIdExists("default")) {
706
- const { removeSystem: removeSystem2 } = await import("./systems-KIQ3KFX3.js");
731
+ const { removeSystem: removeSystem2 } = await import("./systems-JHJ3CQRN.js");
707
732
  removeSystem2("default");
708
733
  }
709
734
  addSystem({
@@ -931,7 +956,7 @@ async function cmdSystemAdd() {
931
956
  default: true
932
957
  });
933
958
  if (shouldRestart) {
934
- const { cmdRestart: cmdRestart2 } = await import("./restart-7ECL2NPC.js");
959
+ const { cmdRestart: cmdRestart2 } = await import("./restart-VIVFUUQG.js");
935
960
  await cmdRestart2({});
936
961
  } else {
937
962
  console.log(` Restart to apply: ${bold("ixora restart")}`);
@@ -1,8 +1,8 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  cmdRestart
4
- } from "./chunk-4XGJZZ73.js";
5
- import "./chunk-VCQRSQ4F.js";
4
+ } from "./chunk-K2GF2QLM.js";
5
+ import "./chunk-KFC4SP2O.js";
6
6
  export {
7
7
  cmdRestart
8
8
  };
@@ -6,7 +6,7 @@ import {
6
6
  systemCount,
7
7
  systemIdExists,
8
8
  totalSystemCount
9
- } from "./chunk-VCQRSQ4F.js";
9
+ } from "./chunk-KFC4SP2O.js";
10
10
  export {
11
11
  addSystem,
12
12
  readSystems,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ibm/ixora",
3
- "version": "0.1.3",
3
+ "version": "0.1.5",
4
4
  "description": "CLI for managing ixora AI agent deployments on IBM i",
5
5
  "type": "module",
6
6
  "bin": {