@kntic/kntic 0.3.0 → 0.4.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kntic/kntic",
3
- "version": "0.3.0",
3
+ "version": "0.4.0",
4
4
  "author": "Thomas Robak <contact@kntic.ai> (https://kntic.ai)",
5
5
  "description": "KNTIC CLI — bootstrap and manage KNTIC projects",
6
6
  "main": "src/index.js",
@@ -1,10 +1,42 @@
1
1
  "use strict";
2
2
 
3
3
  const { execSync } = require("child_process");
4
+ const { readFileSync } = require("fs");
5
+ const { basename } = require("path");
6
+
7
+ function isScreenAvailable() {
8
+ try {
9
+ execSync("which screen", { stdio: "ignore" });
10
+ return true;
11
+ } catch {
12
+ return false;
13
+ }
14
+ }
15
+
16
+ function getScreenName() {
17
+ try {
18
+ const content = readFileSync(".kntic.env", "utf8");
19
+ const match = content.match(/^KNTIC_PRJ_PREFIX=(.+)$/m);
20
+ if (match && match[1].trim()) {
21
+ return match[1].trim();
22
+ }
23
+ } catch {
24
+ // .kntic.env not found or unreadable — fall through
25
+ }
26
+ return basename(process.cwd());
27
+ }
4
28
 
5
29
  function start() {
6
- console.log("Starting KNTIC services…");
7
- execSync("docker compose -f kntic.yml --env-file .kntic.env up --build", { stdio: "inherit" });
30
+ const composeCmd = "docker compose -f kntic.yml --env-file .kntic.env up --build";
31
+
32
+ if (isScreenAvailable()) {
33
+ const screenName = getScreenName();
34
+ console.log(`Starting KNTIC services in screen session "${screenName}"…`);
35
+ execSync(`screen -S ${screenName} ${composeCmd}`, { stdio: "inherit" });
36
+ } else {
37
+ console.log("Starting KNTIC services…");
38
+ execSync(composeCmd, { stdio: "inherit" });
39
+ }
8
40
  }
9
41
 
10
42
  module.exports = start;
@@ -0,0 +1,72 @@
1
+ "use strict";
2
+
3
+ const { describe, it, beforeEach, afterEach } = require("node:test");
4
+ const assert = require("node:assert/strict");
5
+ const fs = require("fs");
6
+ const path = require("path");
7
+ const os = require("os");
8
+
9
+ // We test the helper logic by requiring the module and inspecting behaviour.
10
+ // Since start.js uses execSync (side-effecting), we test the screen-name
11
+ // resolution logic and the screen-detection path indirectly.
12
+
13
+ describe("start — getScreenName resolution", () => {
14
+ let tmpDir, origCwd;
15
+
16
+ beforeEach(() => {
17
+ tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "kntic-start-test-"));
18
+ origCwd = process.cwd();
19
+ process.chdir(tmpDir);
20
+ });
21
+
22
+ afterEach(() => {
23
+ process.chdir(origCwd);
24
+ fs.rmSync(tmpDir, { recursive: true, force: true });
25
+ });
26
+
27
+ it("uses KNTIC_PRJ_PREFIX from .kntic.env when present", () => {
28
+ fs.writeFileSync(path.join(tmpDir, ".kntic.env"), "UID=1000\nKNTIC_PRJ_PREFIX=myproject\nGID=1000\n");
29
+ // Re-require to get fresh module (clear cache)
30
+ delete require.cache[require.resolve("./start")];
31
+ // We can't call start() directly (it execs docker), so we extract getScreenName
32
+ // by reading the source and evaluating the helper in isolation.
33
+ const src = fs.readFileSync(require.resolve("./start"), "utf8");
34
+ const getScreenName = new Function("require", "process",
35
+ src.replace(/^"use strict";\s*/, "")
36
+ .replace(/module\.exports\s*=\s*start;/, "return getScreenName;")
37
+ )(require, process);
38
+ assert.equal(getScreenName(), "myproject");
39
+ });
40
+
41
+ it("falls back to current directory name when KNTIC_PRJ_PREFIX is missing", () => {
42
+ fs.writeFileSync(path.join(tmpDir, ".kntic.env"), "UID=1000\nGID=1000\n");
43
+ delete require.cache[require.resolve("./start")];
44
+ const src = fs.readFileSync(require.resolve("./start"), "utf8");
45
+ const getScreenName = new Function("require", "process",
46
+ src.replace(/^"use strict";\s*/, "")
47
+ .replace(/module\.exports\s*=\s*start;/, "return getScreenName;")
48
+ )(require, process);
49
+ assert.equal(getScreenName(), path.basename(tmpDir));
50
+ });
51
+
52
+ it("falls back to current directory name when .kntic.env does not exist", () => {
53
+ delete require.cache[require.resolve("./start")];
54
+ const src = fs.readFileSync(require.resolve("./start"), "utf8");
55
+ const getScreenName = new Function("require", "process",
56
+ src.replace(/^"use strict";\s*/, "")
57
+ .replace(/module\.exports\s*=\s*start;/, "return getScreenName;")
58
+ )(require, process);
59
+ assert.equal(getScreenName(), path.basename(tmpDir));
60
+ });
61
+
62
+ it("ignores empty KNTIC_PRJ_PREFIX value", () => {
63
+ fs.writeFileSync(path.join(tmpDir, ".kntic.env"), "KNTIC_PRJ_PREFIX=\nUID=1000\n");
64
+ delete require.cache[require.resolve("./start")];
65
+ const src = fs.readFileSync(require.resolve("./start"), "utf8");
66
+ const getScreenName = new Function("require", "process",
67
+ src.replace(/^"use strict";\s*/, "")
68
+ .replace(/module\.exports\s*=\s*start;/, "return getScreenName;")
69
+ )(require, process);
70
+ assert.equal(getScreenName(), path.basename(tmpDir));
71
+ });
72
+ });
@@ -109,6 +109,35 @@ function extractVersion(artifactFilename) {
109
109
  return match[1];
110
110
  }
111
111
 
112
+ /**
113
+ * Update or create the KNTIC_VERSION variable in a .kntic.env file.
114
+ * If the file exists and contains a KNTIC_VERSION line, that line is replaced.
115
+ * If the file exists but has no KNTIC_VERSION, it is appended.
116
+ * If the file does not exist, it is created with the variable.
117
+ *
118
+ * @param {string} version – semantic version string (e.g. "0.0.10")
119
+ * @param {string} envPath – path to .kntic.env (default: ".kntic.env")
120
+ */
121
+ function updateEnvVersion(version, envPath = ".kntic.env") {
122
+ const line = `KNTIC_VERSION=${version}`;
123
+
124
+ if (!fs.existsSync(envPath)) {
125
+ fs.writeFileSync(envPath, line + "\n");
126
+ return;
127
+ }
128
+
129
+ const content = fs.readFileSync(envPath, "utf8");
130
+ const regex = /^KNTIC_VERSION=.*$/m;
131
+
132
+ if (regex.test(content)) {
133
+ const updated = content.replace(regex, line);
134
+ fs.writeFileSync(envPath, updated);
135
+ } else {
136
+ const separator = content.endsWith("\n") ? "" : "\n";
137
+ fs.writeFileSync(envPath, content + separator + line + "\n");
138
+ }
139
+ }
140
+
112
141
  /**
113
142
  * Recursively remove all contents of a directory (but not the directory itself).
114
143
  */
@@ -166,6 +195,9 @@ async function update() {
166
195
  console.log("Updating .kntic/lib …");
167
196
  extractLibOnly(tmpFile, ".");
168
197
 
198
+ // Update KNTIC_VERSION in .kntic.env
199
+ updateEnvVersion(version);
200
+
169
201
  // Clean up
170
202
  fs.unlinkSync(tmpFile);
171
203
 
@@ -177,3 +209,4 @@ module.exports = update;
177
209
  module.exports.extractLibOnly = extractLibOnly;
178
210
  module.exports.extractVersion = extractVersion;
179
211
  module.exports.clearDirectory = clearDirectory;
212
+ module.exports.updateEnvVersion = updateEnvVersion;
@@ -7,7 +7,7 @@ const path = require("path");
7
7
  const os = require("os");
8
8
  const { execSync } = require("child_process");
9
9
 
10
- const { extractLibOnly, extractVersion, clearDirectory } = require("./update");
10
+ const { extractLibOnly, extractVersion, clearDirectory, updateEnvVersion } = require("./update");
11
11
 
12
12
  /**
13
13
  * Helper — create a tar.gz archive in `tmpDir` containing the given files.
@@ -202,6 +202,63 @@ describe("extractLibOnly", () => {
202
202
  });
203
203
  });
204
204
 
205
+ describe("updateEnvVersion", () => {
206
+ let tmpDir;
207
+
208
+ beforeEach(() => {
209
+ tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "kntic-test-env-"));
210
+ });
211
+
212
+ afterEach(() => {
213
+ fs.rmSync(tmpDir, { recursive: true, force: true });
214
+ });
215
+
216
+ it("creates the file with KNTIC_VERSION if it does not exist", () => {
217
+ const envPath = path.join(tmpDir, ".kntic.env");
218
+ updateEnvVersion("1.2.3", envPath);
219
+
220
+ assert.equal(fs.readFileSync(envPath, "utf8"), "KNTIC_VERSION=1.2.3\n");
221
+ });
222
+
223
+ it("appends KNTIC_VERSION if file exists but variable is missing", () => {
224
+ const envPath = path.join(tmpDir, ".kntic.env");
225
+ fs.writeFileSync(envPath, "UID=1000\nGID=1000\n");
226
+
227
+ updateEnvVersion("0.0.10", envPath);
228
+
229
+ const content = fs.readFileSync(envPath, "utf8");
230
+ assert.ok(content.includes("UID=1000"), "existing content must be preserved");
231
+ assert.ok(content.includes("GID=1000"), "existing content must be preserved");
232
+ assert.ok(content.includes("KNTIC_VERSION=0.0.10"), "version must be appended");
233
+ });
234
+
235
+ it("replaces existing KNTIC_VERSION in-place", () => {
236
+ const envPath = path.join(tmpDir, ".kntic.env");
237
+ fs.writeFileSync(envPath, "UID=1000\nKNTIC_VERSION=0.0.1\nGID=1000\n");
238
+
239
+ updateEnvVersion("2.0.0", envPath);
240
+
241
+ const content = fs.readFileSync(envPath, "utf8");
242
+ assert.ok(content.includes("KNTIC_VERSION=2.0.0"), "version must be updated");
243
+ assert.ok(!content.includes("KNTIC_VERSION=0.0.1"), "old version must be gone");
244
+ assert.ok(content.includes("UID=1000"), "other vars must be preserved");
245
+ assert.ok(content.includes("GID=1000"), "other vars must be preserved");
246
+ });
247
+
248
+ it("handles file without trailing newline when appending", () => {
249
+ const envPath = path.join(tmpDir, ".kntic.env");
250
+ fs.writeFileSync(envPath, "UID=1000");
251
+
252
+ updateEnvVersion("0.1.0", envPath);
253
+
254
+ const content = fs.readFileSync(envPath, "utf8");
255
+ assert.ok(content.includes("UID=1000"), "existing content must be preserved");
256
+ assert.ok(content.includes("KNTIC_VERSION=0.1.0"), "version must be appended");
257
+ // Ensure they're on separate lines
258
+ assert.ok(content.includes("UID=1000\nKNTIC_VERSION=0.1.0"), "must be on separate lines");
259
+ });
260
+ });
261
+
205
262
  describe("extractVersion (update module)", () => {
206
263
  it("parses version from artifact filename", () => {
207
264
  assert.equal(extractVersion("kntic-bootstrap-v0.0.10.tar.gz"), "0.0.10");