@someonegg/codex-hub 0.1.9 → 0.2.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/dist/client/assets/{index-BO1xw5sL.css → index-CODhVb8z.css} +1 -1
- package/dist/client/assets/index-CrCuCnqn.js +306 -0
- package/dist/client/index.html +2 -2
- package/dist/server/server/config.js +25 -3
- package/dist/server/server/hosting/diff-store.js +102 -0
- package/dist/server/server/hosting/lock.js +65 -0
- package/dist/server/server/hosting/routes.js +152 -0
- package/dist/server/server/hosting/store.js +158 -0
- package/dist/server/server/http/create-server.js +39 -6
- package/dist/server/server/http/router.js +1 -1
- package/dist/server/server/main.js +55 -0
- package/dist/server/shared/diff.js +158 -0
- package/package.json +3 -2
- package/dist/client/assets/index-BM0ywt3r.js +0 -304
|
@@ -1,11 +1,66 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import { parseArgs } from "node:util";
|
|
2
3
|
import { commandLineHelp, loadConfig } from "./config.js";
|
|
3
4
|
import { createCodexHub } from "./create-hub.js";
|
|
5
|
+
import { DiffStore } from "./hosting/diff-store.js";
|
|
6
|
+
import { HostingStore } from "./hosting/store.js";
|
|
4
7
|
import { createApiRouter } from "./http/api-router.js";
|
|
5
8
|
import { startServer } from "./http/create-server.js";
|
|
6
9
|
import { createTerminalPreviewWebSocket } from "./http/terminal-preview-websocket.js";
|
|
7
10
|
import { uploadDirectory } from "./http/upload-routes.js";
|
|
11
|
+
async function runResourceCommand(resource, action, args) {
|
|
12
|
+
const { values, positionals } = parseArgs({
|
|
13
|
+
args,
|
|
14
|
+
allowPositionals: true,
|
|
15
|
+
options: {
|
|
16
|
+
project: { type: "string" },
|
|
17
|
+
name: { type: "string" },
|
|
18
|
+
title: { type: "string" },
|
|
19
|
+
"hosting-dir": { type: "string" },
|
|
20
|
+
json: { type: "boolean" },
|
|
21
|
+
help: { type: "boolean" },
|
|
22
|
+
},
|
|
23
|
+
});
|
|
24
|
+
if (values.help || action === "--help") {
|
|
25
|
+
console.log(commandLineHelp());
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
if (!["publish", "list", "delete"].includes(action) ||
|
|
29
|
+
positionals.length !== (action === "list" ? 0 : 1))
|
|
30
|
+
throw new Error("Expected page/diff publish <source>, list, or delete <id>");
|
|
31
|
+
const pages = new HostingStore(values["hosting-dir"]);
|
|
32
|
+
const diffs = new DiffStore(values["hosting-dir"]);
|
|
33
|
+
let result;
|
|
34
|
+
if (action === "publish") {
|
|
35
|
+
if (resource === "page" && !values.project)
|
|
36
|
+
throw new Error("page publish requires --project");
|
|
37
|
+
const entry = resource === "page"
|
|
38
|
+
? await pages.publish(positionals[0], values.project, values.name, values.title)
|
|
39
|
+
: await diffs.publish(positionals[0], values.title);
|
|
40
|
+
const title = entry.title.replace(/[\\[\]]/g, "\\$&").replace(/[\r\n]/g, " ");
|
|
41
|
+
console.log(values.json ? JSON.stringify(entry) : `${entry.url}\n[${title}](${entry.url})`);
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
if (action === "list") {
|
|
45
|
+
result =
|
|
46
|
+
resource === "page"
|
|
47
|
+
? (await pages.entries()).map(({ artifact }) => artifact)
|
|
48
|
+
: await diffs.list();
|
|
49
|
+
}
|
|
50
|
+
else {
|
|
51
|
+
if (resource === "page")
|
|
52
|
+
await pages.delete(positionals[0]);
|
|
53
|
+
else
|
|
54
|
+
await diffs.delete(positionals[0]);
|
|
55
|
+
result = { id: positionals[0], deleted: true };
|
|
56
|
+
}
|
|
57
|
+
console.log(JSON.stringify(result, null, values.json ? undefined : 2));
|
|
58
|
+
}
|
|
8
59
|
async function main() {
|
|
60
|
+
if (["page", "diff"].includes(process.argv[2])) {
|
|
61
|
+
await runResourceCommand(process.argv[2], process.argv[3], process.argv.slice(4));
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
9
64
|
const command = loadConfig();
|
|
10
65
|
if (command.kind === "help") {
|
|
11
66
|
console.log(commandLineHelp());
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
const pathEscapes = {
|
|
2
|
+
a: "\x07",
|
|
3
|
+
b: "\b",
|
|
4
|
+
f: "\f",
|
|
5
|
+
v: "\v",
|
|
6
|
+
n: "\n",
|
|
7
|
+
t: "\t",
|
|
8
|
+
r: "\r",
|
|
9
|
+
};
|
|
10
|
+
/** Git quotes paths as C strings, including octal-escaped UTF-8 bytes. */
|
|
11
|
+
function decodePath(value) {
|
|
12
|
+
if (!value.startsWith('"'))
|
|
13
|
+
return value;
|
|
14
|
+
const bytes = [];
|
|
15
|
+
const inner = value.slice(1, value.lastIndexOf('"'));
|
|
16
|
+
for (let i = 0; i < inner.length; i++) {
|
|
17
|
+
if (inner[i] === "\\") {
|
|
18
|
+
const octal = /^[0-7]{1,3}/.exec(inner.slice(i + 1));
|
|
19
|
+
if (octal) {
|
|
20
|
+
bytes.push(parseInt(octal[0], 8));
|
|
21
|
+
i += octal[0].length;
|
|
22
|
+
continue;
|
|
23
|
+
}
|
|
24
|
+
const escape = inner[++i];
|
|
25
|
+
bytes.push(...new TextEncoder().encode(pathEscapes[escape] ?? escape));
|
|
26
|
+
}
|
|
27
|
+
else {
|
|
28
|
+
const point = String.fromCodePoint(inner.codePointAt(i));
|
|
29
|
+
bytes.push(...new TextEncoder().encode(point));
|
|
30
|
+
i += point.length - 1;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
return new TextDecoder().decode(new Uint8Array(bytes));
|
|
34
|
+
}
|
|
35
|
+
function pathName(value) {
|
|
36
|
+
return decodePath(value).replace(/^[ab]\//, "");
|
|
37
|
+
}
|
|
38
|
+
/** Only repeated paths can be inferred reliably without extended headers. */
|
|
39
|
+
function headerPath(value) {
|
|
40
|
+
const quoted = /^"(?:[^"\\]|\\.)*" /.exec(value);
|
|
41
|
+
let oldPath;
|
|
42
|
+
let newPath;
|
|
43
|
+
if (quoted) {
|
|
44
|
+
oldPath = value.slice(0, quoted[0].length - 1);
|
|
45
|
+
newPath = value.slice(quoted[0].length);
|
|
46
|
+
}
|
|
47
|
+
else {
|
|
48
|
+
const quotedNew = / ("(?:[^"\\]|\\.)*")$/.exec(value);
|
|
49
|
+
// Unquoted, identical paths have equal lengths, regardless of embedded spaces.
|
|
50
|
+
const separator = quotedNew?.index ?? (value.length - 1) / 2;
|
|
51
|
+
if (!Number.isInteger(separator) || value[separator] !== " ")
|
|
52
|
+
return null;
|
|
53
|
+
oldPath = value.slice(0, separator);
|
|
54
|
+
newPath = value.slice(separator + 1);
|
|
55
|
+
}
|
|
56
|
+
const oldName = decodePath(oldPath);
|
|
57
|
+
const newName = decodePath(newPath);
|
|
58
|
+
return oldName.startsWith("a/") &&
|
|
59
|
+
newName.startsWith("b/") &&
|
|
60
|
+
oldName.slice(2) === newName.slice(2)
|
|
61
|
+
? newName.slice(2)
|
|
62
|
+
: null;
|
|
63
|
+
}
|
|
64
|
+
export function parseDiff(raw) {
|
|
65
|
+
const files = [];
|
|
66
|
+
let file;
|
|
67
|
+
let pathResolved = false;
|
|
68
|
+
let oldLine = 0;
|
|
69
|
+
let newLine = 0;
|
|
70
|
+
let oldRemaining = 0;
|
|
71
|
+
let newRemaining = 0;
|
|
72
|
+
let inHunk = false;
|
|
73
|
+
let warning = false;
|
|
74
|
+
const finishHunk = () => {
|
|
75
|
+
if (oldRemaining || newRemaining)
|
|
76
|
+
warning = true;
|
|
77
|
+
inHunk = false;
|
|
78
|
+
oldRemaining = newRemaining = 0;
|
|
79
|
+
};
|
|
80
|
+
const finishFile = () => {
|
|
81
|
+
finishHunk();
|
|
82
|
+
if (file && !pathResolved)
|
|
83
|
+
warning = true;
|
|
84
|
+
};
|
|
85
|
+
const lines = raw.split("\n");
|
|
86
|
+
if (lines.at(-1) === "")
|
|
87
|
+
lines.pop();
|
|
88
|
+
for (const text of lines) {
|
|
89
|
+
if (text.startsWith("diff --git ")) {
|
|
90
|
+
finishFile();
|
|
91
|
+
const path = headerPath(text.slice(11));
|
|
92
|
+
pathResolved = path !== null;
|
|
93
|
+
file = {
|
|
94
|
+
path: path ?? text.slice(11),
|
|
95
|
+
lines: [],
|
|
96
|
+
additions: 0,
|
|
97
|
+
deletions: 0,
|
|
98
|
+
};
|
|
99
|
+
files.push(file);
|
|
100
|
+
}
|
|
101
|
+
if (!file) {
|
|
102
|
+
if (text.trim())
|
|
103
|
+
warning = true;
|
|
104
|
+
continue;
|
|
105
|
+
}
|
|
106
|
+
const hunk = /^@@ -(\d+)(?:,(\d+))? \+(\d+)(?:,(\d+))? @@/.exec(text);
|
|
107
|
+
if (hunk) {
|
|
108
|
+
finishHunk();
|
|
109
|
+
oldLine = Number(hunk[1]);
|
|
110
|
+
newLine = Number(hunk[3]);
|
|
111
|
+
oldRemaining = Number(hunk[2] ?? 1);
|
|
112
|
+
newRemaining = Number(hunk[4] ?? 1);
|
|
113
|
+
inHunk = true;
|
|
114
|
+
file.lines.push({ text, kind: "meta" });
|
|
115
|
+
continue;
|
|
116
|
+
}
|
|
117
|
+
if (inHunk && (oldRemaining || newRemaining) && /^[ +\-]/.test(text)) {
|
|
118
|
+
const kind = text[0] === "+" ? "add" : text[0] === "-" ? "remove" : "context";
|
|
119
|
+
const line = { text: text.slice(1), kind };
|
|
120
|
+
if (kind !== "add") {
|
|
121
|
+
line.old = oldLine++;
|
|
122
|
+
oldRemaining--;
|
|
123
|
+
}
|
|
124
|
+
if (kind !== "remove") {
|
|
125
|
+
line.next = newLine++;
|
|
126
|
+
newRemaining--;
|
|
127
|
+
}
|
|
128
|
+
if (oldRemaining < 0 || newRemaining < 0)
|
|
129
|
+
warning = true;
|
|
130
|
+
if (kind === "add")
|
|
131
|
+
file.additions++;
|
|
132
|
+
if (kind === "remove")
|
|
133
|
+
file.deletions++;
|
|
134
|
+
file.lines.push(line);
|
|
135
|
+
continue;
|
|
136
|
+
}
|
|
137
|
+
if (/^(\+\+\+|---) /.test(text)) {
|
|
138
|
+
const path = text.slice(4);
|
|
139
|
+
if (path !== "/dev/null") {
|
|
140
|
+
file.path = pathName(path.replace(/\t$/, ""));
|
|
141
|
+
pathResolved = true;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
const renamed = /^(?:rename|copy) to (.*)$/.exec(text);
|
|
145
|
+
if (renamed) {
|
|
146
|
+
file.path = decodePath(renamed[1]);
|
|
147
|
+
pathResolved = true;
|
|
148
|
+
}
|
|
149
|
+
if (!/^(diff --git |index |--- |\+\+\+ |new file mode |deleted file mode |old mode |new mode |similarity index |dissimilarity index |rename from |rename to |copy from |copy to |Binary files .* differ$|\$)/.test(text))
|
|
150
|
+
warning = true;
|
|
151
|
+
if (inHunk && /^[+\- ]/.test(text))
|
|
152
|
+
warning = true;
|
|
153
|
+
file.lines.push({ text, kind: "meta" });
|
|
154
|
+
}
|
|
155
|
+
finishFile();
|
|
156
|
+
return { files, warning: warning || files.length === 0 };
|
|
157
|
+
}
|
|
158
|
+
//# sourceMappingURL=diff.js.map
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@someonegg/codex-hub",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Local project and session hub for Codex",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"engines": {
|
|
@@ -20,7 +20,8 @@
|
|
|
20
20
|
"format:check": "prettier --check .",
|
|
21
21
|
"lint": "lint-client",
|
|
22
22
|
"prepack": "npm run build",
|
|
23
|
-
"test:package": "node scripts/test-package.mjs"
|
|
23
|
+
"test:package": "node scripts/test-package.mjs",
|
|
24
|
+
"test:hosting": "npm run build && node --import tsx scripts/test-hosting.mjs"
|
|
24
25
|
},
|
|
25
26
|
"keywords": [
|
|
26
27
|
"codex",
|