@kntic/kntic 0.3.1 → 0.4.1

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.1",
3
+ "version": "0.4.1",
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",
package/src/cli.js CHANGED
@@ -21,6 +21,13 @@ if (!subcommand || subcommand === "usage") {
21
21
  console.error(`Error: ${err.message}`);
22
22
  process.exit(1);
23
23
  }
24
+ } else if (subcommand === "stop") {
25
+ try {
26
+ commands.stop();
27
+ } catch (err) {
28
+ console.error(`Error: ${err.message}`);
29
+ process.exit(1);
30
+ }
24
31
  } else if (subcommand === "update") {
25
32
  commands.update().catch((err) => {
26
33
  console.error(`Error: ${err.message}`);
@@ -3,6 +3,7 @@
3
3
  const usage = require("./usage");
4
4
  const init = require("./init");
5
5
  const start = require("./start");
6
+ const stop = require("./stop");
6
7
  const update = require("./update");
7
8
 
8
- module.exports = { usage, init, start, update };
9
+ module.exports = { usage, init, start, stop, update };
@@ -1,10 +1,49 @@
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 isInsideScreen() {
17
+ return !!process.env.STY;
18
+ }
19
+
20
+ function getScreenName() {
21
+ try {
22
+ const content = readFileSync(".kntic.env", "utf8");
23
+ const match = content.match(/^KNTIC_PRJ_PREFIX=(.+)$/m);
24
+ if (match && match[1].trim()) {
25
+ return match[1].trim();
26
+ }
27
+ } catch {
28
+ // .kntic.env not found or unreadable — fall through
29
+ }
30
+ return basename(process.cwd());
31
+ }
4
32
 
5
33
  function start() {
6
- console.log("Starting KNTIC services…");
7
- execSync("docker compose -f kntic.yml --env-file .kntic.env up --build", { stdio: "inherit" });
34
+ const composeCmd = "docker compose -f kntic.yml --env-file .kntic.env up --build";
35
+
36
+ if (isScreenAvailable() && !isInsideScreen()) {
37
+ const screenName = getScreenName();
38
+ console.log(`Starting KNTIC services in screen session "${screenName}"…`);
39
+ execSync(`screen -S ${screenName} ${composeCmd}`, { stdio: "inherit" });
40
+ } else {
41
+ if (isInsideScreen()) {
42
+ console.log("Already inside a screen session, skipping screen wrapper.");
43
+ }
44
+ console.log("Starting KNTIC services…");
45
+ execSync(composeCmd, { stdio: "inherit" });
46
+ }
8
47
  }
9
48
 
10
49
  module.exports = start;
@@ -0,0 +1,88 @@
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
+ // Helper: extract a named function from start.js source by evaluating it in isolation.
10
+ function extractFn(fnName) {
11
+ const src = fs.readFileSync(require.resolve("./start"), "utf8");
12
+ return new Function("require", "process",
13
+ src.replace(/^"use strict";\s*/, "")
14
+ .replace(/module\.exports\s*=\s*start;/, `return ${fnName};`)
15
+ )(require, process);
16
+ }
17
+
18
+ describe("start — getScreenName resolution", () => {
19
+ let tmpDir, origCwd;
20
+
21
+ beforeEach(() => {
22
+ tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "kntic-start-test-"));
23
+ origCwd = process.cwd();
24
+ process.chdir(tmpDir);
25
+ });
26
+
27
+ afterEach(() => {
28
+ process.chdir(origCwd);
29
+ fs.rmSync(tmpDir, { recursive: true, force: true });
30
+ });
31
+
32
+ it("uses KNTIC_PRJ_PREFIX from .kntic.env when present", () => {
33
+ fs.writeFileSync(path.join(tmpDir, ".kntic.env"), "UID=1000\nKNTIC_PRJ_PREFIX=myproject\nGID=1000\n");
34
+ delete require.cache[require.resolve("./start")];
35
+ const getScreenName = extractFn("getScreenName");
36
+ assert.equal(getScreenName(), "myproject");
37
+ });
38
+
39
+ it("falls back to current directory name when KNTIC_PRJ_PREFIX is missing", () => {
40
+ fs.writeFileSync(path.join(tmpDir, ".kntic.env"), "UID=1000\nGID=1000\n");
41
+ delete require.cache[require.resolve("./start")];
42
+ const getScreenName = extractFn("getScreenName");
43
+ assert.equal(getScreenName(), path.basename(tmpDir));
44
+ });
45
+
46
+ it("falls back to current directory name when .kntic.env does not exist", () => {
47
+ delete require.cache[require.resolve("./start")];
48
+ const getScreenName = extractFn("getScreenName");
49
+ assert.equal(getScreenName(), path.basename(tmpDir));
50
+ });
51
+
52
+ it("ignores empty KNTIC_PRJ_PREFIX value", () => {
53
+ fs.writeFileSync(path.join(tmpDir, ".kntic.env"), "KNTIC_PRJ_PREFIX=\nUID=1000\n");
54
+ delete require.cache[require.resolve("./start")];
55
+ const getScreenName = extractFn("getScreenName");
56
+ assert.equal(getScreenName(), path.basename(tmpDir));
57
+ });
58
+ });
59
+
60
+ describe("start — isInsideScreen detection", () => {
61
+ let origSTY;
62
+
63
+ beforeEach(() => {
64
+ origSTY = process.env.STY;
65
+ });
66
+
67
+ afterEach(() => {
68
+ if (origSTY === undefined) {
69
+ delete process.env.STY;
70
+ } else {
71
+ process.env.STY = origSTY;
72
+ }
73
+ });
74
+
75
+ it("returns true when STY environment variable is set", () => {
76
+ process.env.STY = "12345.myscreen";
77
+ delete require.cache[require.resolve("./start")];
78
+ const isInsideScreen = extractFn("isInsideScreen");
79
+ assert.equal(isInsideScreen(), true);
80
+ });
81
+
82
+ it("returns false when STY environment variable is not set", () => {
83
+ delete process.env.STY;
84
+ delete require.cache[require.resolve("./start")];
85
+ const isInsideScreen = extractFn("isInsideScreen");
86
+ assert.equal(isInsideScreen(), false);
87
+ });
88
+ });
@@ -0,0 +1,11 @@
1
+ "use strict";
2
+
3
+ const { execSync } = require("child_process");
4
+
5
+ function stop() {
6
+ const composeCmd = "docker compose -f kntic.yml --env-file .kntic.env stop";
7
+ console.log("Stopping KNTIC services…");
8
+ execSync(composeCmd, { stdio: "inherit" });
9
+ }
10
+
11
+ module.exports = stop;
@@ -0,0 +1,17 @@
1
+ "use strict";
2
+
3
+ const { describe, it } = require("node:test");
4
+ const assert = require("node:assert/strict");
5
+ const fs = require("fs");
6
+
7
+ describe("stop — module structure", () => {
8
+ it("exports a function", () => {
9
+ const stop = require("./stop");
10
+ assert.equal(typeof stop, "function");
11
+ });
12
+
13
+ it("source contains the correct docker compose stop command", () => {
14
+ const src = fs.readFileSync(require.resolve("./stop"), "utf8");
15
+ assert.ok(src.includes("docker compose -f kntic.yml --env-file .kntic.env stop"));
16
+ });
17
+ });
@@ -7,6 +7,7 @@ function usage() {
7
7
  console.log(" usage List all available sub-commands");
8
8
  console.log(" init Download and extract the KNTIC bootstrap template into the current directory");
9
9
  console.log(" start Build and start KNTIC services via docker compose (uses kntic.yml + .kntic.env)");
10
+ console.log(" stop Stop KNTIC services via docker compose");
10
11
  console.log(" update Download the latest KNTIC bootstrap and update .kntic/lib only");
11
12
  console.log("");
12
13
  }