@kntic/kntic 0.7.0 → 0.9.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/README.md +3 -3
- package/package.json +1 -1
- package/src/cli.js +1 -1
- package/src/commands/init.js +32 -6
- package/src/commands/init.test.js +62 -10
- package/src/commands/start.js +3 -1
- package/src/commands/start.test.js +32 -0
- package/src/commands/stop.js +3 -1
- package/src/commands/stop.test.js +38 -3
- package/src/commands/usage.js +2 -2
- package/src/commands/utils.js +22 -0
package/README.md
CHANGED
|
@@ -42,13 +42,13 @@ kntic usage
|
|
|
42
42
|
Download and extract the KNTIC bootstrap template into the current directory. Sets up the `.kntic/` directory structure, `kntic.yml`, and `.kntic.env`.
|
|
43
43
|
|
|
44
44
|
```bash
|
|
45
|
-
kntic init [--quick | --interactive | -i]
|
|
45
|
+
kntic init [--quick | -q | --interactive | -i]
|
|
46
46
|
```
|
|
47
47
|
|
|
48
48
|
| Option | Description |
|
|
49
49
|
|--------|-------------|
|
|
50
|
-
| `--
|
|
51
|
-
| `--
|
|
50
|
+
| `--interactive`, `-i` | **Default.** Walks through all `.kntic.env` values interactively, prompting for each variable. Auto-detected values are pre-filled as defaults. Skips `KNTIC_VERSION` and already-detected `GITLAB_TOKEN`. |
|
|
51
|
+
| `--quick`, `-q` | Non-interactive mode. Auto-detects `GIT_HOST` and `GIT_REPO_PATH` from the git origin remote (SSH and HTTPS). Extracts `GITLAB_TOKEN` from HTTPS credentials if available (`glpat-*` tokens). |
|
|
52
52
|
|
|
53
53
|
**What it does:**
|
|
54
54
|
|
package/package.json
CHANGED
package/src/cli.js
CHANGED
|
@@ -10,7 +10,7 @@ const subcommand = args[0];
|
|
|
10
10
|
if (!subcommand || subcommand === "usage") {
|
|
11
11
|
commands.usage();
|
|
12
12
|
} else if (subcommand === "init") {
|
|
13
|
-
const initOpts = { interactive: args.includes("--
|
|
13
|
+
const initOpts = { interactive: !(args.includes("--quick") || args.includes("-q")) };
|
|
14
14
|
commands.init(initOpts).catch((err) => {
|
|
15
15
|
console.error(`Error: ${err.message}`);
|
|
16
16
|
process.exit(1);
|
package/src/commands/init.js
CHANGED
|
@@ -273,10 +273,11 @@ function interactiveEnvSetup(envPath) {
|
|
|
273
273
|
/**
|
|
274
274
|
* Run preflight checks before downloading the bootstrap archive.
|
|
275
275
|
* All checks are warnings only — they never throw or exit.
|
|
276
|
-
* Returns
|
|
276
|
+
* Returns { warnings: string[], composeProvider: string|null }.
|
|
277
277
|
*/
|
|
278
278
|
function preflightChecks() {
|
|
279
279
|
const warnings = [];
|
|
280
|
+
let composeProvider = null;
|
|
280
281
|
|
|
281
282
|
// 1. Linux platform check
|
|
282
283
|
if (process.platform !== "linux") {
|
|
@@ -285,11 +286,31 @@ function preflightChecks() {
|
|
|
285
286
|
);
|
|
286
287
|
}
|
|
287
288
|
|
|
288
|
-
// 2.
|
|
289
|
+
// 2. Compose provider detection
|
|
290
|
+
let hasDocker = false;
|
|
291
|
+
let hasPodman = false;
|
|
289
292
|
try {
|
|
290
293
|
execSync("which docker", { stdio: "ignore" });
|
|
291
|
-
|
|
292
|
-
|
|
294
|
+
hasDocker = true;
|
|
295
|
+
} catch {}
|
|
296
|
+
if (!hasDocker) {
|
|
297
|
+
try {
|
|
298
|
+
execSync("which podman", { stdio: "ignore" });
|
|
299
|
+
hasPodman = true;
|
|
300
|
+
} catch {}
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
if (hasDocker) {
|
|
304
|
+
composeProvider = "docker compose";
|
|
305
|
+
} else if (hasPodman) {
|
|
306
|
+
composeProvider = "podman-compose";
|
|
307
|
+
try {
|
|
308
|
+
execSync("which podman-compose", { stdio: "ignore" });
|
|
309
|
+
} catch {
|
|
310
|
+
warnings.push("⚠ podman-compose not found — install it via `pip install podman-compose`");
|
|
311
|
+
}
|
|
312
|
+
} else {
|
|
313
|
+
warnings.push("⚠ No container runtime found (docker or podman) — required for `kntic start`");
|
|
293
314
|
}
|
|
294
315
|
|
|
295
316
|
// 3. screen binary check
|
|
@@ -303,12 +324,12 @@ function preflightChecks() {
|
|
|
303
324
|
console.log(w);
|
|
304
325
|
}
|
|
305
326
|
|
|
306
|
-
return warnings;
|
|
327
|
+
return { warnings, composeProvider };
|
|
307
328
|
}
|
|
308
329
|
|
|
309
330
|
async function init(options = {}) {
|
|
310
331
|
// Run preflight checks before anything else
|
|
311
|
-
preflightChecks();
|
|
332
|
+
const { composeProvider } = preflightChecks();
|
|
312
333
|
|
|
313
334
|
// Resolve current version from the artifact metadata file
|
|
314
335
|
const artifactFilename = await fetchText(BOOTSTRAP_ARTIFACT_URL);
|
|
@@ -328,6 +349,11 @@ async function init(options = {}) {
|
|
|
328
349
|
// Clean up
|
|
329
350
|
fs.unlinkSync(tmpFile);
|
|
330
351
|
|
|
352
|
+
// Auto-fill compose provider
|
|
353
|
+
if (composeProvider) {
|
|
354
|
+
fillEnvValues('.kntic.env', { KNTIC_COMPOSE_PROVIDER: composeProvider });
|
|
355
|
+
}
|
|
356
|
+
|
|
331
357
|
// Auto-detect git remote
|
|
332
358
|
const gitInfo = parseGitRemote();
|
|
333
359
|
|
|
@@ -47,7 +47,7 @@ describe("preflightChecks", () => {
|
|
|
47
47
|
|
|
48
48
|
it("warns when platform is not linux", () => {
|
|
49
49
|
Object.defineProperty(process, "platform", { value: "darwin", configurable: true });
|
|
50
|
-
const warnings = preflightChecks();
|
|
50
|
+
const { warnings } = preflightChecks();
|
|
51
51
|
const platformWarning = warnings.find((w) => w.includes("Non-Linux detected"));
|
|
52
52
|
assert.ok(platformWarning, "should warn about non-linux platform");
|
|
53
53
|
assert.ok(platformWarning.includes("darwin"));
|
|
@@ -55,24 +55,76 @@ describe("preflightChecks", () => {
|
|
|
55
55
|
|
|
56
56
|
it("does not warn about platform on linux", () => {
|
|
57
57
|
Object.defineProperty(process, "platform", { value: "linux", configurable: true });
|
|
58
|
-
const warnings = preflightChecks();
|
|
58
|
+
const { warnings } = preflightChecks();
|
|
59
59
|
const platformWarning = warnings.find((w) => w.includes("Non-Linux detected"));
|
|
60
60
|
assert.equal(platformWarning, undefined, "should not warn on linux");
|
|
61
61
|
});
|
|
62
62
|
|
|
63
|
-
it("
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
63
|
+
it("returns correct structure with warnings and composeProvider", () => {
|
|
64
|
+
const result = preflightChecks();
|
|
65
|
+
assert.ok(Array.isArray(result.warnings), "warnings must be an array");
|
|
66
|
+
// composeProvider is either a string or null
|
|
67
|
+
assert.ok(
|
|
68
|
+
result.composeProvider === null || typeof result.composeProvider === "string",
|
|
69
|
+
"composeProvider must be string or null"
|
|
70
|
+
);
|
|
71
|
+
for (const w of result.warnings) {
|
|
69
72
|
assert.equal(typeof w, "string");
|
|
70
73
|
}
|
|
71
74
|
});
|
|
72
75
|
|
|
76
|
+
it("returns composeProvider 'docker compose' when docker is found", () => {
|
|
77
|
+
// On the test system docker may or may not exist. We verify the shape.
|
|
78
|
+
const { composeProvider } = preflightChecks();
|
|
79
|
+
// If docker is on PATH, provider should be 'docker compose'
|
|
80
|
+
let hasDocker = false;
|
|
81
|
+
try {
|
|
82
|
+
execSync("which docker", { stdio: "ignore" });
|
|
83
|
+
hasDocker = true;
|
|
84
|
+
} catch {}
|
|
85
|
+
if (hasDocker) {
|
|
86
|
+
assert.equal(composeProvider, "docker compose");
|
|
87
|
+
}
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
it("returns composeProvider 'podman-compose' when only podman is found", () => {
|
|
91
|
+
// This test verifies the detection logic structurally.
|
|
92
|
+
// On most CI systems docker is present, so we just verify the return shape.
|
|
93
|
+
const { composeProvider } = preflightChecks();
|
|
94
|
+
// If neither docker nor podman, composeProvider is null
|
|
95
|
+
let hasDocker = false;
|
|
96
|
+
let hasPodman = false;
|
|
97
|
+
try { execSync("which docker", { stdio: "ignore" }); hasDocker = true; } catch {}
|
|
98
|
+
if (!hasDocker) {
|
|
99
|
+
try { execSync("which podman", { stdio: "ignore" }); hasPodman = true; } catch {}
|
|
100
|
+
}
|
|
101
|
+
if (!hasDocker && hasPodman) {
|
|
102
|
+
assert.equal(composeProvider, "podman-compose");
|
|
103
|
+
}
|
|
104
|
+
if (!hasDocker && !hasPodman) {
|
|
105
|
+
assert.equal(composeProvider, null);
|
|
106
|
+
}
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
it("warns when neither docker nor podman found", () => {
|
|
110
|
+
// We verify the warning message format if it appears.
|
|
111
|
+
const { warnings, composeProvider } = preflightChecks();
|
|
112
|
+
let hasDocker = false;
|
|
113
|
+
let hasPodman = false;
|
|
114
|
+
try { execSync("which docker", { stdio: "ignore" }); hasDocker = true; } catch {}
|
|
115
|
+
if (!hasDocker) {
|
|
116
|
+
try { execSync("which podman", { stdio: "ignore" }); hasPodman = true; } catch {}
|
|
117
|
+
}
|
|
118
|
+
if (!hasDocker && !hasPodman) {
|
|
119
|
+
assert.equal(composeProvider, null);
|
|
120
|
+
const runtimeWarning = warnings.find((w) => w.includes("No container runtime found"));
|
|
121
|
+
assert.ok(runtimeWarning, "should warn about missing container runtime");
|
|
122
|
+
}
|
|
123
|
+
});
|
|
124
|
+
|
|
73
125
|
it("each warning is independent (all checks run)", () => {
|
|
74
126
|
Object.defineProperty(process, "platform", { value: "win32", configurable: true });
|
|
75
|
-
const warnings = preflightChecks();
|
|
127
|
+
const { warnings } = preflightChecks();
|
|
76
128
|
// Platform warning must be present regardless of binary check results
|
|
77
129
|
const platformWarning = warnings.find((w) => w.includes("Non-Linux detected"));
|
|
78
130
|
assert.ok(platformWarning, "platform warning must be present");
|
|
@@ -83,7 +135,7 @@ describe("preflightChecks", () => {
|
|
|
83
135
|
|
|
84
136
|
it("prints warnings to console.log", () => {
|
|
85
137
|
Object.defineProperty(process, "platform", { value: "freebsd", configurable: true });
|
|
86
|
-
const warnings = preflightChecks();
|
|
138
|
+
const { warnings } = preflightChecks();
|
|
87
139
|
// At least the platform warning should be logged
|
|
88
140
|
const platformLog = logMessages.find((m) => m.includes("Non-Linux detected"));
|
|
89
141
|
assert.ok(platformLog, "platform warning must be printed via console.log");
|
package/src/commands/start.js
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
const { execSync } = require("child_process");
|
|
4
4
|
const { readFileSync } = require("fs");
|
|
5
5
|
const { basename } = require("path");
|
|
6
|
+
const { getComposeProvider } = require("./utils");
|
|
6
7
|
|
|
7
8
|
function isScreenAvailable() {
|
|
8
9
|
try {
|
|
@@ -31,7 +32,8 @@ function getScreenName() {
|
|
|
31
32
|
}
|
|
32
33
|
|
|
33
34
|
function start(options = {}) {
|
|
34
|
-
const
|
|
35
|
+
const provider = getComposeProvider();
|
|
36
|
+
const composeCmd = `${provider} -f kntic.yml --env-file .kntic.env up --build`;
|
|
35
37
|
const useScreen = !!options.screen;
|
|
36
38
|
|
|
37
39
|
if (useScreen && isScreenAvailable() && !isInsideScreen()) {
|
|
@@ -57,6 +57,38 @@ describe("start — getScreenName resolution", () => {
|
|
|
57
57
|
});
|
|
58
58
|
});
|
|
59
59
|
|
|
60
|
+
describe("start — compose provider from .kntic.env", () => {
|
|
61
|
+
let tmpDir, origCwd;
|
|
62
|
+
|
|
63
|
+
beforeEach(() => {
|
|
64
|
+
tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "kntic-start-compose-"));
|
|
65
|
+
origCwd = process.cwd();
|
|
66
|
+
process.chdir(tmpDir);
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
afterEach(() => {
|
|
70
|
+
process.chdir(origCwd);
|
|
71
|
+
fs.rmSync(tmpDir, { recursive: true, force: true });
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
it("reads KNTIC_COMPOSE_PROVIDER from .kntic.env", () => {
|
|
75
|
+
fs.writeFileSync(path.join(tmpDir, ".kntic.env"), "KNTIC_COMPOSE_PROVIDER=podman-compose\nUID=1000\n");
|
|
76
|
+
const { getComposeProvider } = require("./utils");
|
|
77
|
+
assert.equal(getComposeProvider(), "podman-compose");
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
it("defaults to 'docker compose' when KNTIC_COMPOSE_PROVIDER is missing", () => {
|
|
81
|
+
fs.writeFileSync(path.join(tmpDir, ".kntic.env"), "UID=1000\nGID=1000\n");
|
|
82
|
+
const { getComposeProvider } = require("./utils");
|
|
83
|
+
assert.equal(getComposeProvider(), "docker compose");
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
it("defaults to 'docker compose' when .kntic.env does not exist", () => {
|
|
87
|
+
const { getComposeProvider } = require("./utils");
|
|
88
|
+
assert.equal(getComposeProvider(), "docker compose");
|
|
89
|
+
});
|
|
90
|
+
});
|
|
91
|
+
|
|
60
92
|
describe("start — isInsideScreen detection", () => {
|
|
61
93
|
let origSTY;
|
|
62
94
|
|
package/src/commands/stop.js
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
|
|
3
3
|
const { execSync } = require("child_process");
|
|
4
|
+
const { getComposeProvider } = require("./utils");
|
|
4
5
|
|
|
5
6
|
function stop() {
|
|
6
|
-
const
|
|
7
|
+
const provider = getComposeProvider();
|
|
8
|
+
const composeCmd = `${provider} -f kntic.yml --env-file .kntic.env stop`;
|
|
7
9
|
console.log("Stopping KNTIC services…");
|
|
8
10
|
execSync(composeCmd, { stdio: "inherit" });
|
|
9
11
|
}
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
|
|
3
|
-
const { describe, it } = require("node:test");
|
|
3
|
+
const { describe, it, beforeEach, afterEach } = require("node:test");
|
|
4
4
|
const assert = require("node:assert/strict");
|
|
5
5
|
const fs = require("fs");
|
|
6
|
+
const path = require("path");
|
|
7
|
+
const os = require("os");
|
|
6
8
|
|
|
7
9
|
describe("stop — module structure", () => {
|
|
8
10
|
it("exports a function", () => {
|
|
@@ -10,8 +12,41 @@ describe("stop — module structure", () => {
|
|
|
10
12
|
assert.equal(typeof stop, "function");
|
|
11
13
|
});
|
|
12
14
|
|
|
13
|
-
it("source
|
|
15
|
+
it("source uses getComposeProvider for compose command", () => {
|
|
14
16
|
const src = fs.readFileSync(require.resolve("./stop"), "utf8");
|
|
15
|
-
assert.ok(src.includes("
|
|
17
|
+
assert.ok(src.includes("getComposeProvider"), "should use getComposeProvider helper");
|
|
18
|
+
assert.ok(src.includes("-f kntic.yml --env-file .kntic.env stop"), "should include compose flags");
|
|
19
|
+
});
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
describe("stop — compose provider from .kntic.env", () => {
|
|
23
|
+
let tmpDir, origCwd;
|
|
24
|
+
|
|
25
|
+
beforeEach(() => {
|
|
26
|
+
tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "kntic-stop-compose-"));
|
|
27
|
+
origCwd = process.cwd();
|
|
28
|
+
process.chdir(tmpDir);
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
afterEach(() => {
|
|
32
|
+
process.chdir(origCwd);
|
|
33
|
+
fs.rmSync(tmpDir, { recursive: true, force: true });
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it("reads KNTIC_COMPOSE_PROVIDER from .kntic.env", () => {
|
|
37
|
+
fs.writeFileSync(path.join(tmpDir, ".kntic.env"), "KNTIC_COMPOSE_PROVIDER=podman-compose\nUID=1000\n");
|
|
38
|
+
const { getComposeProvider } = require("./utils");
|
|
39
|
+
assert.equal(getComposeProvider(), "podman-compose");
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it("defaults to 'docker compose' when KNTIC_COMPOSE_PROVIDER is missing", () => {
|
|
43
|
+
fs.writeFileSync(path.join(tmpDir, ".kntic.env"), "UID=1000\nGID=1000\n");
|
|
44
|
+
const { getComposeProvider } = require("./utils");
|
|
45
|
+
assert.equal(getComposeProvider(), "docker compose");
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it("defaults to 'docker compose' when .kntic.env does not exist", () => {
|
|
49
|
+
const { getComposeProvider } = require("./utils");
|
|
50
|
+
assert.equal(getComposeProvider(), "docker compose");
|
|
16
51
|
});
|
|
17
52
|
});
|
package/src/commands/usage.js
CHANGED
|
@@ -6,8 +6,8 @@ function usage() {
|
|
|
6
6
|
console.log("Available commands:\n");
|
|
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
|
-
console.log(" --
|
|
10
|
-
console.log(" --interactive
|
|
9
|
+
console.log(" --interactive Default mode — walk through .kntic.env values interactively (-i)");
|
|
10
|
+
console.log(" --quick Non-interactive mode, auto-detects git remote only (-q)");
|
|
11
11
|
console.log(" start Build and start KNTIC services via docker compose (uses kntic.yml + .kntic.env)");
|
|
12
12
|
console.log(" --screen Run inside a GNU screen session");
|
|
13
13
|
console.log(" stop Stop KNTIC services via docker compose");
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
const fs = require("fs");
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Read KNTIC_COMPOSE_PROVIDER from .kntic.env.
|
|
7
|
+
* Returns the value or 'docker compose' as fallback.
|
|
8
|
+
*/
|
|
9
|
+
function getComposeProvider() {
|
|
10
|
+
try {
|
|
11
|
+
const content = fs.readFileSync(".kntic.env", "utf8");
|
|
12
|
+
const match = content.match(/^KNTIC_COMPOSE_PROVIDER=(.+)$/m);
|
|
13
|
+
if (match && match[1].trim()) {
|
|
14
|
+
return match[1].trim();
|
|
15
|
+
}
|
|
16
|
+
} catch {
|
|
17
|
+
// .kntic.env not found or unreadable — fall through
|
|
18
|
+
}
|
|
19
|
+
return "docker compose";
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
module.exports = { getComposeProvider };
|