@textcortex/zenocode 0.1.10 → 0.1.11
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": "@textcortex/zenocode",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.11",
|
|
4
4
|
"description": "Secure, EU-hosted coding agent for TextCortex customers that runs in your terminal, edits files, runs scripts, and more.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"ai",
|
|
@@ -30,6 +30,7 @@
|
|
|
30
30
|
"scripts": {
|
|
31
31
|
"build-branded-opencode": "node scripts/build-branded-opencode.mjs",
|
|
32
32
|
"prepare-config": "node scripts/run-zenocode.mjs --prepare-only",
|
|
33
|
+
"prepare-release-version": "node scripts/prepare-release-version.mjs",
|
|
33
34
|
"dev": "node scripts/run-zenocode.mjs",
|
|
34
35
|
"login": "node scripts/run-zenocode.mjs login",
|
|
35
36
|
"logout": "node scripts/run-zenocode.mjs logout"
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import fs from "node:fs/promises";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import process from "node:process";
|
|
5
|
+
import { fileURLToPath } from "node:url";
|
|
6
|
+
|
|
7
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
8
|
+
const defaultPackageJsonPath = path.resolve(__dirname, "..", "package.json");
|
|
9
|
+
|
|
10
|
+
export function parseSemver(value) {
|
|
11
|
+
const match = value.match(/^(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z.-]+))?(?:\+.*)?$/);
|
|
12
|
+
if (!match) {
|
|
13
|
+
throw new Error(`Invalid semver: ${value}`);
|
|
14
|
+
}
|
|
15
|
+
return {
|
|
16
|
+
major: Number(match[1]),
|
|
17
|
+
minor: Number(match[2]),
|
|
18
|
+
patch: Number(match[3]),
|
|
19
|
+
prerelease: match[4] ?? null,
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function compareSemver(left, right) {
|
|
24
|
+
const lhs = parseSemver(left);
|
|
25
|
+
const rhs = parseSemver(right);
|
|
26
|
+
|
|
27
|
+
for (const key of ["major", "minor", "patch"]) {
|
|
28
|
+
if (lhs[key] > rhs[key]) return 1;
|
|
29
|
+
if (lhs[key] < rhs[key]) return -1;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
if (lhs.prerelease === rhs.prerelease) return 0;
|
|
33
|
+
if (lhs.prerelease === null) return 1;
|
|
34
|
+
if (rhs.prerelease === null) return -1;
|
|
35
|
+
|
|
36
|
+
const leftParts = lhs.prerelease.split(".");
|
|
37
|
+
const rightParts = rhs.prerelease.split(".");
|
|
38
|
+
const length = Math.max(leftParts.length, rightParts.length);
|
|
39
|
+
for (let index = 0; index < length; index += 1) {
|
|
40
|
+
const leftPart = leftParts[index];
|
|
41
|
+
const rightPart = rightParts[index];
|
|
42
|
+
if (leftPart === undefined) return -1;
|
|
43
|
+
if (rightPart === undefined) return 1;
|
|
44
|
+
const leftNumeric = /^\d+$/.test(leftPart);
|
|
45
|
+
const rightNumeric = /^\d+$/.test(rightPart);
|
|
46
|
+
if (leftNumeric && rightNumeric) {
|
|
47
|
+
const leftValue = Number(leftPart);
|
|
48
|
+
const rightValue = Number(rightPart);
|
|
49
|
+
if (leftValue > rightValue) return 1;
|
|
50
|
+
if (leftValue < rightValue) return -1;
|
|
51
|
+
continue;
|
|
52
|
+
}
|
|
53
|
+
if (leftNumeric && !rightNumeric) return -1;
|
|
54
|
+
if (!leftNumeric && rightNumeric) return 1;
|
|
55
|
+
if (leftPart > rightPart) return 1;
|
|
56
|
+
if (leftPart < rightPart) return -1;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return 0;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export function incrementPatchVersion(version) {
|
|
63
|
+
const parsed = parseSemver(version);
|
|
64
|
+
return `${parsed.major}.${parsed.minor}.${parsed.patch + 1}`;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export function resolveReleaseVersion(localVersion, publishedVersion) {
|
|
68
|
+
if (!publishedVersion) {
|
|
69
|
+
return localVersion;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const comparison = compareSemver(localVersion, publishedVersion);
|
|
73
|
+
if (comparison > 0) {
|
|
74
|
+
return localVersion;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
return incrementPatchVersion(publishedVersion);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export async function prepareReleasePackageVersion({
|
|
81
|
+
packageJsonPath = defaultPackageJsonPath,
|
|
82
|
+
publishedVersion = null,
|
|
83
|
+
} = {}) {
|
|
84
|
+
const packageJson = JSON.parse(await fs.readFile(packageJsonPath, "utf-8"));
|
|
85
|
+
const packageName = String(packageJson.name || "").trim();
|
|
86
|
+
const localVersion = String(packageJson.version || "").trim();
|
|
87
|
+
|
|
88
|
+
if (!packageName) {
|
|
89
|
+
throw new Error(`Missing package name in ${packageJsonPath}`);
|
|
90
|
+
}
|
|
91
|
+
if (!localVersion) {
|
|
92
|
+
throw new Error(`Missing package version in ${packageJsonPath}`);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
parseSemver(localVersion);
|
|
96
|
+
if (publishedVersion) {
|
|
97
|
+
parseSemver(publishedVersion);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const nextVersion = resolveReleaseVersion(localVersion, publishedVersion);
|
|
101
|
+
const updated = nextVersion !== localVersion;
|
|
102
|
+
|
|
103
|
+
if (updated) {
|
|
104
|
+
packageJson.version = nextVersion;
|
|
105
|
+
await fs.writeFile(packageJsonPath, `${JSON.stringify(packageJson, null, 2)}\n`, "utf-8");
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
return {
|
|
109
|
+
packageName,
|
|
110
|
+
localVersion,
|
|
111
|
+
nextVersion,
|
|
112
|
+
publishedVersion,
|
|
113
|
+
updated,
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
export function parseArgs(argv) {
|
|
118
|
+
const options = {
|
|
119
|
+
packageJsonPath: defaultPackageJsonPath,
|
|
120
|
+
publishedVersion: null,
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
for (let index = 0; index < argv.length; index += 1) {
|
|
124
|
+
const arg = argv[index];
|
|
125
|
+
if (arg === "--package-json") {
|
|
126
|
+
if (!argv[index + 1]) {
|
|
127
|
+
throw new Error("--package-json requires a value");
|
|
128
|
+
}
|
|
129
|
+
options.packageJsonPath = path.resolve(argv[index + 1]);
|
|
130
|
+
index += 1;
|
|
131
|
+
continue;
|
|
132
|
+
}
|
|
133
|
+
if (arg === "--published-version") {
|
|
134
|
+
options.publishedVersion = (argv[index + 1] || "").trim() || null;
|
|
135
|
+
index += 1;
|
|
136
|
+
continue;
|
|
137
|
+
}
|
|
138
|
+
throw new Error(`Unknown argument: ${arg}`);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
return options;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
async function main() {
|
|
145
|
+
const result = await prepareReleasePackageVersion(parseArgs(process.argv.slice(2)));
|
|
146
|
+
console.log(
|
|
147
|
+
JSON.stringify(
|
|
148
|
+
{
|
|
149
|
+
package_name: result.packageName,
|
|
150
|
+
local_version: result.localVersion,
|
|
151
|
+
next_version: result.nextVersion,
|
|
152
|
+
published_version: result.publishedVersion,
|
|
153
|
+
updated: result.updated,
|
|
154
|
+
},
|
|
155
|
+
null,
|
|
156
|
+
2,
|
|
157
|
+
),
|
|
158
|
+
);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
const invokedPath = process.argv[1] ? path.resolve(process.argv[1]) : "";
|
|
162
|
+
if (invokedPath === fileURLToPath(import.meta.url)) {
|
|
163
|
+
await main();
|
|
164
|
+
}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import fs from "node:fs/promises";
|
|
3
|
+
import os from "node:os";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import test from "node:test";
|
|
6
|
+
|
|
7
|
+
import {
|
|
8
|
+
parseArgs,
|
|
9
|
+
prepareReleasePackageVersion,
|
|
10
|
+
resolveReleaseVersion,
|
|
11
|
+
} from "./prepare-release-version.mjs";
|
|
12
|
+
|
|
13
|
+
test("resolveReleaseVersion keeps the local version when nothing is published yet", () => {
|
|
14
|
+
assert.equal(resolveReleaseVersion("0.1.10", null), "0.1.10");
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
test("resolveReleaseVersion bumps the patch version when the local version matches the published version", () => {
|
|
18
|
+
assert.equal(resolveReleaseVersion("0.1.10", "0.1.10"), "0.1.11");
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
test("resolveReleaseVersion bumps from the published version when the local version is behind", () => {
|
|
22
|
+
assert.equal(resolveReleaseVersion("0.1.9", "0.1.10"), "0.1.11");
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
test("resolveReleaseVersion keeps an already-ahead local version", () => {
|
|
26
|
+
assert.equal(resolveReleaseVersion("0.1.11", "0.1.10"), "0.1.11");
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
test("resolveReleaseVersion rejects invalid semver values", () => {
|
|
30
|
+
assert.throws(() => resolveReleaseVersion("invalid", "0.1.10"), /Invalid semver/);
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
test("prepareReleasePackageVersion rewrites package.json when an automatic bump is required", async () => {
|
|
34
|
+
const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), "zenocode-version-"));
|
|
35
|
+
const packageJsonPath = path.join(tempDir, "package.json");
|
|
36
|
+
await fs.writeFile(
|
|
37
|
+
packageJsonPath,
|
|
38
|
+
`${JSON.stringify(
|
|
39
|
+
{
|
|
40
|
+
name: "@textcortex/zenocode",
|
|
41
|
+
version: "0.1.10",
|
|
42
|
+
},
|
|
43
|
+
null,
|
|
44
|
+
2,
|
|
45
|
+
)}\n`,
|
|
46
|
+
"utf-8",
|
|
47
|
+
);
|
|
48
|
+
|
|
49
|
+
const result = await prepareReleasePackageVersion({
|
|
50
|
+
packageJsonPath,
|
|
51
|
+
publishedVersion: "0.1.10",
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
const packageJson = JSON.parse(await fs.readFile(packageJsonPath, "utf-8"));
|
|
55
|
+
assert.deepEqual(result, {
|
|
56
|
+
packageName: "@textcortex/zenocode",
|
|
57
|
+
localVersion: "0.1.10",
|
|
58
|
+
nextVersion: "0.1.11",
|
|
59
|
+
publishedVersion: "0.1.10",
|
|
60
|
+
updated: true,
|
|
61
|
+
});
|
|
62
|
+
assert.equal(packageJson.version, "0.1.11");
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
test("prepareReleasePackageVersion leaves package.json untouched when the local version is already ahead", async () => {
|
|
66
|
+
const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), "zenocode-version-"));
|
|
67
|
+
const packageJsonPath = path.join(tempDir, "package.json");
|
|
68
|
+
const originalContents = `${JSON.stringify(
|
|
69
|
+
{
|
|
70
|
+
name: "@textcortex/zenocode",
|
|
71
|
+
version: "0.1.11",
|
|
72
|
+
},
|
|
73
|
+
null,
|
|
74
|
+
2,
|
|
75
|
+
)}\n`;
|
|
76
|
+
await fs.writeFile(packageJsonPath, originalContents, "utf-8");
|
|
77
|
+
|
|
78
|
+
const result = await prepareReleasePackageVersion({
|
|
79
|
+
packageJsonPath,
|
|
80
|
+
publishedVersion: "0.1.10",
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
const packageJson = await fs.readFile(packageJsonPath, "utf-8");
|
|
84
|
+
assert.deepEqual(result, {
|
|
85
|
+
packageName: "@textcortex/zenocode",
|
|
86
|
+
localVersion: "0.1.11",
|
|
87
|
+
nextVersion: "0.1.11",
|
|
88
|
+
publishedVersion: "0.1.10",
|
|
89
|
+
updated: false,
|
|
90
|
+
});
|
|
91
|
+
assert.equal(packageJson, originalContents);
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
test("parseArgs requires a value after --package-json", () => {
|
|
95
|
+
assert.throws(() => parseArgs(["--package-json"]), /--package-json requires a value/);
|
|
96
|
+
});
|
package/scripts/run-zenocode.mjs
CHANGED
|
@@ -576,6 +576,16 @@ export function resolveTextCortexBaseUrl({
|
|
|
576
576
|
return envBaseUrl || storedBaseUrl || cloudBaseUrlDefault;
|
|
577
577
|
}
|
|
578
578
|
|
|
579
|
+
export function resolveLoginBaseUrl({
|
|
580
|
+
envBaseUrl,
|
|
581
|
+
preferLocalhost = false,
|
|
582
|
+
} = {}) {
|
|
583
|
+
if (preferLocalhost) {
|
|
584
|
+
return localBaseUrlDefault;
|
|
585
|
+
}
|
|
586
|
+
return envBaseUrl || cloudBaseUrlDefault;
|
|
587
|
+
}
|
|
588
|
+
|
|
579
589
|
function _loginConnectivityHelp(baseUrl) {
|
|
580
590
|
return [
|
|
581
591
|
`Cannot reach Zenocode auth endpoint at ${baseUrl}.`,
|
|
@@ -1578,7 +1588,14 @@ async function main() {
|
|
|
1578
1588
|
const subcommand = runtimeArgs[0];
|
|
1579
1589
|
|
|
1580
1590
|
if (subcommand === "login") {
|
|
1581
|
-
await runLoginCommand(
|
|
1591
|
+
await runLoginCommand(
|
|
1592
|
+
resolveLoginBaseUrl({
|
|
1593
|
+
envBaseUrl: process.env.TEXTCORTEX_BASE_URL,
|
|
1594
|
+
preferLocalhost,
|
|
1595
|
+
}),
|
|
1596
|
+
runtimeArgs.slice(1),
|
|
1597
|
+
{ preferLocalhost },
|
|
1598
|
+
);
|
|
1582
1599
|
return;
|
|
1583
1600
|
}
|
|
1584
1601
|
|
|
@@ -16,6 +16,7 @@ import {
|
|
|
16
16
|
buildZenocodeBanner,
|
|
17
17
|
chooseDefaults,
|
|
18
18
|
hasLocalBaseUrlFlag,
|
|
19
|
+
resolveLoginBaseUrl,
|
|
19
20
|
resolveLoginSuccessIdentifier,
|
|
20
21
|
resolveTextCortexBaseUrl,
|
|
21
22
|
runRuntimeWithSessionRecovery,
|
|
@@ -67,6 +68,34 @@ test("resolveTextCortexBaseUrl prefers localhost when the local flag is enabled"
|
|
|
67
68
|
);
|
|
68
69
|
});
|
|
69
70
|
|
|
71
|
+
test("resolveLoginBaseUrl ignores stored credentials and defaults explicit login to cloud", () => {
|
|
72
|
+
assert.equal(
|
|
73
|
+
resolveLoginBaseUrl({
|
|
74
|
+
envBaseUrl: "",
|
|
75
|
+
}),
|
|
76
|
+
"https://api.textcortex.com",
|
|
77
|
+
);
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
test("resolveLoginBaseUrl still respects explicit env overrides", () => {
|
|
81
|
+
assert.equal(
|
|
82
|
+
resolveLoginBaseUrl({
|
|
83
|
+
envBaseUrl: "https://staging.textcortex.com",
|
|
84
|
+
}),
|
|
85
|
+
"https://staging.textcortex.com",
|
|
86
|
+
);
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
test("resolveLoginBaseUrl prefers localhost when the local flag is enabled", () => {
|
|
90
|
+
assert.equal(
|
|
91
|
+
resolveLoginBaseUrl({
|
|
92
|
+
envBaseUrl: "https://staging.textcortex.com",
|
|
93
|
+
preferLocalhost: true,
|
|
94
|
+
}),
|
|
95
|
+
"http://127.0.0.1:8080",
|
|
96
|
+
);
|
|
97
|
+
});
|
|
98
|
+
|
|
70
99
|
test("hasLocalBaseUrlFlag detects localhost flags", () => {
|
|
71
100
|
assert.equal(hasLocalBaseUrlFlag(["login", "--local"]), true);
|
|
72
101
|
assert.equal(hasLocalBaseUrlFlag(["--localhost", "run"]), true);
|