@spencer-kit/coder-studio 0.4.0 → 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/CHANGELOG.md +7 -0
- package/dist/esm/bin.mjs +12749 -11945
- package/dist/esm/bin.mjs.map +4 -4
- package/dist/esm/server-runner.mjs +12706 -11893
- package/dist/esm/server-runner.mjs.map +4 -4
- package/dist/esm/update-worker.mjs +156 -0
- package/dist/esm/update-worker.mjs.map +7 -0
- package/dist/web/assets/index-BkUU2b7M.css +1 -0
- package/dist/web/assets/index-g7vn9KmI.js +111 -0
- package/dist/web/assets/index-g7vn9KmI.js.map +1 -0
- package/dist/web/index.html +2 -2
- package/package.json +1 -1
- package/src/auth-control.test.ts +3 -3
- package/src/auth-control.ts +6 -6
- package/src/bin.test.ts +24 -12
- package/src/cli.ts +7 -7
- package/src/config-store.test.ts +57 -14
- package/src/config-store.ts +25 -15
- package/src/package-manifest.ts +5 -0
- package/src/parse-args.ts +5 -4
- package/src/server-runner.test.ts +36 -5
- package/src/server-runner.ts +8 -7
- package/src/update-runtime.test.ts +13 -0
- package/src/update-runtime.ts +44 -0
- package/src/update-worker.test.ts +99 -0
- package/src/update-worker.ts +213 -0
- package/dist/web/assets/index-CNoLfuZU.js +0 -112
- package/dist/web/assets/index-CNoLfuZU.js.map +0 -1
- package/dist/web/assets/index-cMGhBE_V.css +0 -1
package/dist/web/index.html
CHANGED
|
@@ -6,13 +6,13 @@
|
|
|
6
6
|
<meta name="description" content="Coder Studio - Agent-First Development Environment" />
|
|
7
7
|
<title>Coder Studio</title>
|
|
8
8
|
<link rel="icon" type="image/x-icon" href="/favicon.ico" />
|
|
9
|
-
<script type="module" crossorigin src="/assets/index-
|
|
9
|
+
<script type="module" crossorigin src="/assets/index-g7vn9KmI.js"></script>
|
|
10
10
|
<link rel="modulepreload" crossorigin href="/assets/rolldown-runtime-S-ySWqyJ.js">
|
|
11
11
|
<link rel="modulepreload" crossorigin href="/assets/monaco-editor-PK4VJ5X2.js">
|
|
12
12
|
<link rel="modulepreload" crossorigin href="/assets/xterm-0bvFymvt.js">
|
|
13
13
|
<link rel="stylesheet" crossorigin href="/assets/monaco-editor-Br_kD0ds.css">
|
|
14
14
|
<link rel="stylesheet" crossorigin href="/assets/xterm-BrP-ENHg.css">
|
|
15
|
-
<link rel="stylesheet" crossorigin href="/assets/index-
|
|
15
|
+
<link rel="stylesheet" crossorigin href="/assets/index-BkUU2b7M.css">
|
|
16
16
|
</head>
|
|
17
17
|
<body>
|
|
18
18
|
<div id="root"></div>
|
package/package.json
CHANGED
package/src/auth-control.test.ts
CHANGED
|
@@ -8,19 +8,19 @@ describe("auth-control", () => {
|
|
|
8
8
|
const originalHome = process.env.HOME;
|
|
9
9
|
const originalUserProfile = process.env.USERPROFILE;
|
|
10
10
|
let testHomeDir: string;
|
|
11
|
-
let
|
|
11
|
+
let legacyStateFilePath: string;
|
|
12
12
|
let authBlocksPath: string;
|
|
13
13
|
|
|
14
14
|
beforeEach(() => {
|
|
15
15
|
testHomeDir = mkdtempSync(join(tmpdir(), "cs-auth-control-home-"));
|
|
16
16
|
process.env.HOME = testHomeDir;
|
|
17
17
|
process.env.USERPROFILE = testHomeDir;
|
|
18
|
-
|
|
18
|
+
legacyStateFilePath = join(testHomeDir, "legacy-state.sqlite");
|
|
19
19
|
authBlocksPath = join(testHomeDir, "state", "auth-login-blocks.json");
|
|
20
20
|
mkdirSync(join(testHomeDir, ".coder-studio"), { recursive: true });
|
|
21
21
|
writeFileSync(
|
|
22
22
|
join(testHomeDir, ".coder-studio", "config.json"),
|
|
23
|
-
JSON.stringify({ dataDir:
|
|
23
|
+
JSON.stringify({ dataDir: legacyStateFilePath }, null, 2),
|
|
24
24
|
"utf-8"
|
|
25
25
|
);
|
|
26
26
|
});
|
package/src/auth-control.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { join } from "node:path";
|
|
2
2
|
import { AuthLoginBlockRepo, parseServerConfig } from "@coder-studio/server";
|
|
3
3
|
import { readCliConfig } from "./config-store.js";
|
|
4
4
|
|
|
@@ -10,16 +10,16 @@ export interface CliAuthBlock {
|
|
|
10
10
|
blockedUntil: number;
|
|
11
11
|
}
|
|
12
12
|
|
|
13
|
-
function
|
|
13
|
+
function resolveStateDir(): string {
|
|
14
14
|
const savedConfig = readCliConfig();
|
|
15
15
|
return parseServerConfig({
|
|
16
|
-
...(savedConfig?.
|
|
17
|
-
}).
|
|
16
|
+
...(savedConfig?.stateDir !== undefined ? { stateDir: savedConfig.stateDir } : {}),
|
|
17
|
+
}).stateDir;
|
|
18
18
|
}
|
|
19
19
|
|
|
20
20
|
export async function listAuthBlocks(now = Date.now()): Promise<CliAuthBlock[]> {
|
|
21
21
|
const repo = new AuthLoginBlockRepo({
|
|
22
|
-
filePath: join(
|
|
22
|
+
filePath: join(resolveStateDir(), "state", "auth-login-blocks.json"),
|
|
23
23
|
});
|
|
24
24
|
return repo.listActiveBlocks(now).map((record) => ({
|
|
25
25
|
ip: record.ip,
|
|
@@ -32,7 +32,7 @@ export async function listAuthBlocks(now = Date.now()): Promise<CliAuthBlock[]>
|
|
|
32
32
|
|
|
33
33
|
export async function clearAuthBlockByIp(ip: string): Promise<boolean> {
|
|
34
34
|
const repo = new AuthLoginBlockRepo({
|
|
35
|
-
filePath: join(
|
|
35
|
+
filePath: join(resolveStateDir(), "state", "auth-login-blocks.json"),
|
|
36
36
|
});
|
|
37
37
|
return repo.delete(ip);
|
|
38
38
|
}
|
package/src/bin.test.ts
CHANGED
|
@@ -500,7 +500,7 @@ describe("main", () => {
|
|
|
500
500
|
readCliConfig.mockReturnValue({
|
|
501
501
|
host: "0.0.0.0",
|
|
502
502
|
port: 0,
|
|
503
|
-
|
|
503
|
+
stateDir: "/tmp/cs-data",
|
|
504
504
|
password: "sekrit",
|
|
505
505
|
});
|
|
506
506
|
|
|
@@ -508,7 +508,7 @@ describe("main", () => {
|
|
|
508
508
|
|
|
509
509
|
expect(writeCliConfig).toHaveBeenCalledWith({
|
|
510
510
|
host: "127.0.0.1",
|
|
511
|
-
|
|
511
|
+
stateDir: "/tmp/cs-data",
|
|
512
512
|
password: "sekrit",
|
|
513
513
|
});
|
|
514
514
|
});
|
|
@@ -577,9 +577,17 @@ describe("parseArgs", () => {
|
|
|
577
577
|
});
|
|
578
578
|
|
|
579
579
|
it("parses config command with data-dir and password values", () => {
|
|
580
|
+
expect(parseArgs(["config", "--state-dir", "/tmp/cs-data", "--password", "sekrit"])).toEqual({
|
|
581
|
+
command: "config",
|
|
582
|
+
stateDir: "/tmp/cs-data",
|
|
583
|
+
password: "sekrit",
|
|
584
|
+
});
|
|
585
|
+
});
|
|
586
|
+
|
|
587
|
+
it("accepts legacy data-dir as a config alias", () => {
|
|
580
588
|
expect(parseArgs(["config", "--data-dir", "/tmp/cs-data", "--password", "sekrit"])).toEqual({
|
|
581
589
|
command: "config",
|
|
582
|
-
|
|
590
|
+
stateDir: "/tmp/cs-data",
|
|
583
591
|
password: "sekrit",
|
|
584
592
|
});
|
|
585
593
|
});
|
|
@@ -705,14 +713,18 @@ describe("parseArgs", () => {
|
|
|
705
713
|
expect(() => parseArgs(["serve", "--port", "4186"])).toThrow(RUNTIME_CONFIG_ERROR);
|
|
706
714
|
});
|
|
707
715
|
|
|
708
|
-
it("rejects serve-time
|
|
709
|
-
expect(() => parseArgs(["serve", "--
|
|
716
|
+
it("rejects serve-time state-dir overrides", () => {
|
|
717
|
+
expect(() => parseArgs(["serve", "--state-dir", "/tmp/data"])).toThrow(RUNTIME_CONFIG_ERROR);
|
|
710
718
|
});
|
|
711
719
|
|
|
712
|
-
it("rejects bare data-dir overrides", () => {
|
|
720
|
+
it("rejects bare legacy data-dir overrides", () => {
|
|
713
721
|
expect(() => parseArgs(["--data-dir", "/tmp/cs-data"])).toThrow(RUNTIME_CONFIG_ERROR);
|
|
714
722
|
});
|
|
715
723
|
|
|
724
|
+
it("rejects bare state-dir overrides", () => {
|
|
725
|
+
expect(() => parseArgs(["--state-dir", "/tmp/cs-data"])).toThrow(RUNTIME_CONFIG_ERROR);
|
|
726
|
+
});
|
|
727
|
+
|
|
716
728
|
it("rejects serve-time password overrides", () => {
|
|
717
729
|
expect(() => parseArgs(["serve", "--password", "sekrit"])).toThrow(RUNTIME_CONFIG_ERROR);
|
|
718
730
|
});
|
|
@@ -757,9 +769,9 @@ describe("parseArgs", () => {
|
|
|
757
769
|
expect(() => parseArgs(["logs", "--tail", "10junk"])).toThrow("Invalid tail number");
|
|
758
770
|
});
|
|
759
771
|
|
|
760
|
-
it("rejects stop-time
|
|
761
|
-
expect(() => parseArgs(["stop", "--
|
|
762
|
-
"Unknown option: --
|
|
772
|
+
it("rejects stop-time state-dir overrides", () => {
|
|
773
|
+
expect(() => parseArgs(["stop", "--state-dir", "/tmp/cs-data"])).toThrow(
|
|
774
|
+
"Unknown option: --state-dir"
|
|
763
775
|
);
|
|
764
776
|
});
|
|
765
777
|
|
|
@@ -839,10 +851,10 @@ describe("parseArgs", () => {
|
|
|
839
851
|
});
|
|
840
852
|
});
|
|
841
853
|
|
|
842
|
-
it("allows config-time
|
|
843
|
-
expect(parseArgs(["config", "--
|
|
854
|
+
it("allows config-time state-dir updates", () => {
|
|
855
|
+
expect(parseArgs(["config", "--state-dir", "/custom/path"])).toEqual({
|
|
844
856
|
command: "config",
|
|
845
|
-
|
|
857
|
+
stateDir: "/custom/path",
|
|
846
858
|
});
|
|
847
859
|
});
|
|
848
860
|
|
package/src/cli.ts
CHANGED
|
@@ -71,7 +71,7 @@ COMMANDS:
|
|
|
71
71
|
server Alias for serve
|
|
72
72
|
open Start the server if needed and open Coder Studio in a browser
|
|
73
73
|
auth Manage auth login blocks in local server storage
|
|
74
|
-
config Persist CLI host/port/
|
|
74
|
+
config Persist CLI host/port/state-dir/password settings
|
|
75
75
|
stop Stop the managed Coder Studio server
|
|
76
76
|
status Show the managed server status
|
|
77
77
|
logs Show the managed server logs
|
|
@@ -81,7 +81,7 @@ COMMANDS:
|
|
|
81
81
|
OPTIONS:
|
|
82
82
|
--host <string> Save server host for future runs
|
|
83
83
|
--port, -p <number> Save server port for future runs
|
|
84
|
-
--data-dir, -d <path>
|
|
84
|
+
--state-dir, --data-dir, -d <path> Save state directory for future runs
|
|
85
85
|
--password <string> Save auth password for future runs
|
|
86
86
|
--restart Restart an already running managed server for serve/open
|
|
87
87
|
--help Show help
|
|
@@ -119,7 +119,7 @@ BEHAVIOR:
|
|
|
119
119
|
OPTIONS:
|
|
120
120
|
--host <string> Save server host for future runs
|
|
121
121
|
--port, -p <number> Save server port for future runs
|
|
122
|
-
--data-dir, -d <path>
|
|
122
|
+
--state-dir, --data-dir, -d <path> Save state directory for future runs
|
|
123
123
|
--password <string> Save auth password for future runs
|
|
124
124
|
--help Show config help
|
|
125
125
|
|
|
@@ -127,7 +127,7 @@ EXAMPLES:
|
|
|
127
127
|
coder-studio config
|
|
128
128
|
coder-studio config --host 0.0.0.0
|
|
129
129
|
coder-studio config --port 8080
|
|
130
|
-
coder-studio config --
|
|
130
|
+
coder-studio config --state-dir /tmp/cs-data
|
|
131
131
|
coder-studio config --password sekrit
|
|
132
132
|
coder-studio config --host 0.0.0.0 --port 8080
|
|
133
133
|
`);
|
|
@@ -246,7 +246,7 @@ export async function main(argv = process.argv.slice(2)): Promise<void> {
|
|
|
246
246
|
if (
|
|
247
247
|
args.host === undefined &&
|
|
248
248
|
args.port === undefined &&
|
|
249
|
-
args.
|
|
249
|
+
args.stateDir === undefined &&
|
|
250
250
|
args.password === undefined
|
|
251
251
|
) {
|
|
252
252
|
console.log(formatConfig(readCliConfig()));
|
|
@@ -259,11 +259,11 @@ export async function main(argv = process.argv.slice(2)): Promise<void> {
|
|
|
259
259
|
...(savedConfig?.port !== undefined && savedConfig.port > 0
|
|
260
260
|
? { port: savedConfig.port }
|
|
261
261
|
: {}),
|
|
262
|
-
...(savedConfig?.
|
|
262
|
+
...(savedConfig?.stateDir !== undefined ? { stateDir: savedConfig.stateDir } : {}),
|
|
263
263
|
...(savedConfig?.password !== undefined ? { password: savedConfig.password } : {}),
|
|
264
264
|
...(args.host !== undefined ? { host: args.host } : {}),
|
|
265
265
|
...(args.port !== undefined ? { port: args.port } : {}),
|
|
266
|
-
...(args.
|
|
266
|
+
...(args.stateDir !== undefined ? { stateDir: args.stateDir } : {}),
|
|
267
267
|
...(args.password !== undefined ? { password: args.password } : {}),
|
|
268
268
|
};
|
|
269
269
|
writeCliConfig(nextConfig);
|
package/src/config-store.test.ts
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import { existsSync, mkdtempSync, readFileSync, rmSync } from "fs";
|
|
1
|
+
import { existsSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "fs";
|
|
2
2
|
import { tmpdir } from "os";
|
|
3
3
|
import { join } from "path";
|
|
4
4
|
import { afterEach, beforeEach, describe, expect, it } from "vitest";
|
|
5
5
|
import {
|
|
6
6
|
type CliConfig,
|
|
7
7
|
getCliConfigPath,
|
|
8
|
-
|
|
8
|
+
normalizeLegacyDataDir,
|
|
9
9
|
readCliConfig,
|
|
10
10
|
writeCliConfig,
|
|
11
11
|
} from "./config-store.js";
|
|
@@ -43,43 +43,86 @@ describe("config-store", () => {
|
|
|
43
43
|
expect(readCliConfig()).toBeNull();
|
|
44
44
|
});
|
|
45
45
|
|
|
46
|
-
it("writes and reads host port
|
|
47
|
-
const config
|
|
46
|
+
it("writes and reads host port state-dir and password config", () => {
|
|
47
|
+
const config = {
|
|
48
48
|
host: "0.0.0.0",
|
|
49
49
|
port: 4186,
|
|
50
|
-
|
|
50
|
+
stateDir: "/tmp/cs-data",
|
|
51
51
|
password: "sekrit",
|
|
52
52
|
};
|
|
53
53
|
|
|
54
|
-
writeCliConfig(config);
|
|
54
|
+
writeCliConfig(config as CliConfig);
|
|
55
55
|
|
|
56
56
|
expect(readCliConfig()).toEqual(config);
|
|
57
57
|
});
|
|
58
58
|
|
|
59
|
-
it("
|
|
60
|
-
expect(
|
|
59
|
+
it("keeps a directory input as the state directory", () => {
|
|
60
|
+
expect(normalizeLegacyDataDir("/tmp/cs-data")).toBe("/tmp/cs-data");
|
|
61
61
|
});
|
|
62
62
|
|
|
63
|
-
it("
|
|
64
|
-
expect(
|
|
63
|
+
it("normalizes a legacy sqlite file path to its parent state directory", () => {
|
|
64
|
+
expect(normalizeLegacyDataDir("/tmp/cs-data/custom.sqlite")).toBe("/tmp/cs-data");
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
it("preserves a saved stateDir that ends with .db", () => {
|
|
68
|
+
writeCliConfig({
|
|
69
|
+
stateDir: "/tmp/modern-state/custom-dir.db",
|
|
70
|
+
} as CliConfig);
|
|
71
|
+
|
|
72
|
+
expect(readCliConfig()).toEqual({
|
|
73
|
+
stateDir: "/tmp/modern-state/custom-dir.db",
|
|
74
|
+
});
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it("preserves an in-memory saved stateDir", () => {
|
|
78
|
+
writeCliConfig({
|
|
79
|
+
stateDir: ":memory:",
|
|
80
|
+
} as CliConfig);
|
|
81
|
+
|
|
82
|
+
expect(readCliConfig()).toEqual({
|
|
83
|
+
stateDir: ":memory:",
|
|
84
|
+
});
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
it("reads legacy dataDir config as stateDir", () => {
|
|
88
|
+
writeCliConfig({
|
|
89
|
+
host: "127.0.0.1",
|
|
90
|
+
port: 4186,
|
|
91
|
+
stateDir: "/tmp/modern-state",
|
|
92
|
+
password: "sekrit",
|
|
93
|
+
} as CliConfig);
|
|
94
|
+
|
|
95
|
+
const configPath = getCliConfigPath();
|
|
96
|
+
const stored = JSON.parse(readFileSync(configPath, "utf-8")) as Record<string, unknown>;
|
|
97
|
+
stored.dataDir = "/tmp/legacy-state/legacy-state.sqlite";
|
|
98
|
+
delete stored.stateDir;
|
|
99
|
+
|
|
100
|
+
writeFileSync(configPath, JSON.stringify(stored, null, 2), "utf-8");
|
|
101
|
+
|
|
102
|
+
expect(readCliConfig()).toEqual({
|
|
103
|
+
host: "127.0.0.1",
|
|
104
|
+
port: 4186,
|
|
105
|
+
stateDir: "/tmp/legacy-state",
|
|
106
|
+
password: "sekrit",
|
|
107
|
+
});
|
|
65
108
|
});
|
|
66
109
|
|
|
67
110
|
it("does not persist ephemeral port zero in config", () => {
|
|
68
111
|
writeCliConfig({
|
|
69
112
|
host: "127.0.0.1",
|
|
70
113
|
port: 0,
|
|
71
|
-
|
|
114
|
+
stateDir: "/tmp/cs-data",
|
|
72
115
|
password: "sekrit",
|
|
73
|
-
});
|
|
116
|
+
} as CliConfig);
|
|
74
117
|
|
|
75
118
|
expect(JSON.parse(readFileSync(getCliConfigPath(), "utf-8"))).toEqual({
|
|
76
119
|
host: "127.0.0.1",
|
|
77
|
-
|
|
120
|
+
stateDir: "/tmp/cs-data",
|
|
78
121
|
password: "sekrit",
|
|
79
122
|
});
|
|
80
123
|
expect(readCliConfig()).toEqual({
|
|
81
124
|
host: "127.0.0.1",
|
|
82
|
-
|
|
125
|
+
stateDir: "/tmp/cs-data",
|
|
83
126
|
password: "sekrit",
|
|
84
127
|
});
|
|
85
128
|
});
|
package/src/config-store.ts
CHANGED
|
@@ -1,13 +1,12 @@
|
|
|
1
|
+
import { normalizeLegacyStateDir, normalizeStateDir } from "@coder-studio/core/state-paths";
|
|
1
2
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
|
|
2
3
|
import { homedir } from "os";
|
|
3
|
-
import {
|
|
4
|
-
|
|
5
|
-
const DEFAULT_STATE_ANCHOR_FILE = "coder-studio.db";
|
|
4
|
+
import { join } from "path";
|
|
6
5
|
|
|
7
6
|
export interface CliConfig {
|
|
8
7
|
host?: string;
|
|
9
8
|
port?: number;
|
|
10
|
-
|
|
9
|
+
stateDir?: string;
|
|
11
10
|
password?: string;
|
|
12
11
|
}
|
|
13
12
|
|
|
@@ -15,14 +14,8 @@ export function getCliConfigPath(): string {
|
|
|
15
14
|
return join(homedir(), ".coder-studio", "config.json");
|
|
16
15
|
}
|
|
17
16
|
|
|
18
|
-
export function
|
|
19
|
-
|
|
20
|
-
return input;
|
|
21
|
-
}
|
|
22
|
-
if (basename(input).includes(".")) {
|
|
23
|
-
return input;
|
|
24
|
-
}
|
|
25
|
-
return join(input, DEFAULT_STATE_ANCHOR_FILE);
|
|
17
|
+
export function normalizeLegacyDataDir(input: string): string {
|
|
18
|
+
return normalizeLegacyStateDir(input);
|
|
26
19
|
}
|
|
27
20
|
|
|
28
21
|
export function readCliConfig(): CliConfig | null {
|
|
@@ -32,16 +25,33 @@ export function readCliConfig(): CliConfig | null {
|
|
|
32
25
|
}
|
|
33
26
|
|
|
34
27
|
try {
|
|
35
|
-
const parsed = JSON.parse(readFileSync(path, "utf-8")) as
|
|
28
|
+
const parsed = JSON.parse(readFileSync(path, "utf-8")) as {
|
|
29
|
+
host?: unknown;
|
|
30
|
+
port?: unknown;
|
|
31
|
+
stateDir?: unknown;
|
|
32
|
+
dataDir?: unknown;
|
|
33
|
+
password?: unknown;
|
|
34
|
+
};
|
|
36
35
|
if (
|
|
37
36
|
(parsed.host !== undefined && typeof parsed.host !== "string") ||
|
|
38
37
|
(parsed.port !== undefined && typeof parsed.port !== "number") ||
|
|
38
|
+
(parsed.stateDir !== undefined && typeof parsed.stateDir !== "string") ||
|
|
39
39
|
(parsed.dataDir !== undefined && typeof parsed.dataDir !== "string") ||
|
|
40
40
|
(parsed.password !== undefined && typeof parsed.password !== "string")
|
|
41
41
|
) {
|
|
42
42
|
return null;
|
|
43
43
|
}
|
|
44
|
-
|
|
44
|
+
|
|
45
|
+
return {
|
|
46
|
+
...(parsed.host !== undefined ? { host: parsed.host } : {}),
|
|
47
|
+
...(parsed.port !== undefined ? { port: parsed.port } : {}),
|
|
48
|
+
...(parsed.stateDir !== undefined
|
|
49
|
+
? { stateDir: normalizeStateDir(parsed.stateDir) }
|
|
50
|
+
: parsed.dataDir !== undefined
|
|
51
|
+
? { stateDir: normalizeLegacyDataDir(parsed.dataDir) }
|
|
52
|
+
: {}),
|
|
53
|
+
...(parsed.password !== undefined ? { password: parsed.password } : {}),
|
|
54
|
+
};
|
|
45
55
|
} catch {
|
|
46
56
|
return null;
|
|
47
57
|
}
|
|
@@ -53,7 +63,7 @@ export function writeCliConfig(config: CliConfig): void {
|
|
|
53
63
|
const normalizedConfig: CliConfig = {
|
|
54
64
|
...(config.host !== undefined ? { host: config.host } : {}),
|
|
55
65
|
...(config.port !== undefined && config.port > 0 ? { port: config.port } : {}),
|
|
56
|
-
...(config.
|
|
66
|
+
...(config.stateDir !== undefined ? { stateDir: normalizeStateDir(config.stateDir) } : {}),
|
|
57
67
|
...(config.password !== undefined ? { password: config.password } : {}),
|
|
58
68
|
};
|
|
59
69
|
if (!existsSync(dir)) {
|
package/src/package-manifest.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { existsSync, readFileSync } from "fs";
|
|
2
2
|
|
|
3
3
|
interface CliPackageManifest {
|
|
4
|
+
name?: string;
|
|
4
5
|
version?: string;
|
|
5
6
|
}
|
|
6
7
|
|
|
@@ -26,3 +27,7 @@ export function getCliPackageManifest(importMetaUrl: string): CliPackageManifest
|
|
|
26
27
|
export function getCliVersion(importMetaUrl: string): string {
|
|
27
28
|
return getCliPackageManifest(importMetaUrl).version ?? "0.0.0";
|
|
28
29
|
}
|
|
30
|
+
|
|
31
|
+
export function getCliPackageName(importMetaUrl: string): string {
|
|
32
|
+
return getCliPackageManifest(importMetaUrl).name ?? "@spencer-kit/coder-studio";
|
|
33
|
+
}
|
package/src/parse-args.ts
CHANGED
|
@@ -11,7 +11,7 @@ type CliCommand =
|
|
|
11
11
|
type AuthCommand = "ban-list" | "unblock";
|
|
12
12
|
|
|
13
13
|
export const RUNTIME_CONFIG_ERROR =
|
|
14
|
-
"Host, port,
|
|
14
|
+
"Host, port, state-dir, password, and auth settings must be configured via the config command";
|
|
15
15
|
|
|
16
16
|
export interface CliArgs {
|
|
17
17
|
foreground?: boolean;
|
|
@@ -23,7 +23,7 @@ export interface CliArgs {
|
|
|
23
23
|
configHelp?: boolean;
|
|
24
24
|
port?: number;
|
|
25
25
|
host?: string;
|
|
26
|
-
|
|
26
|
+
stateDir?: string;
|
|
27
27
|
password?: string;
|
|
28
28
|
noAuth?: boolean;
|
|
29
29
|
ip?: string;
|
|
@@ -37,7 +37,7 @@ function clearConfigArgs(args: CliArgs): void {
|
|
|
37
37
|
delete args.configHelp;
|
|
38
38
|
delete args.port;
|
|
39
39
|
delete args.host;
|
|
40
|
-
delete args.
|
|
40
|
+
delete args.stateDir;
|
|
41
41
|
delete args.password;
|
|
42
42
|
delete args.noAuth;
|
|
43
43
|
}
|
|
@@ -217,10 +217,11 @@ export function parseArgs(argv: string[]): CliArgs {
|
|
|
217
217
|
i += 1;
|
|
218
218
|
break;
|
|
219
219
|
|
|
220
|
+
case "--state-dir":
|
|
220
221
|
case "--data-dir":
|
|
221
222
|
case "-d":
|
|
222
223
|
ensureConfigContext(args, arg);
|
|
223
|
-
args.
|
|
224
|
+
args.stateDir = readOptionValue(argv, i + 1, "state-dir");
|
|
224
225
|
i += 1;
|
|
225
226
|
break;
|
|
226
227
|
|
|
@@ -1,6 +1,10 @@
|
|
|
1
|
+
import { existsSync, mkdtempSync, rmSync } from "fs";
|
|
2
|
+
import { tmpdir } from "os";
|
|
3
|
+
import { join } from "path";
|
|
1
4
|
import { fileURLToPath } from "url";
|
|
2
5
|
import { afterEach, describe, expect, it, vi } from "vitest";
|
|
3
6
|
import { getCliVersion } from "./package-manifest.js";
|
|
7
|
+
import { getUpdateRuntimeInfo } from "./update-runtime.js";
|
|
4
8
|
|
|
5
9
|
const { createServer, parseServerConfig, readCliConfig, hasWebAssets, getStaticAssetsDir } =
|
|
6
10
|
vi.hoisted(() => ({
|
|
@@ -45,6 +49,7 @@ describe("server-runner", () => {
|
|
|
45
49
|
|
|
46
50
|
expect(buildServerConfig()).toMatchObject({
|
|
47
51
|
appVersion: getCliVersion(import.meta.url),
|
|
52
|
+
update: getUpdateRuntimeInfo(import.meta.url),
|
|
48
53
|
webRoot: "/tmp/web",
|
|
49
54
|
});
|
|
50
55
|
});
|
|
@@ -53,7 +58,7 @@ describe("server-runner", () => {
|
|
|
53
58
|
readCliConfig.mockReturnValue({
|
|
54
59
|
host: "127.0.0.1",
|
|
55
60
|
port: 0,
|
|
56
|
-
|
|
61
|
+
stateDir: "/tmp/cs-data",
|
|
57
62
|
password: "sekrit",
|
|
58
63
|
});
|
|
59
64
|
hasWebAssets.mockReturnValue(true);
|
|
@@ -61,8 +66,9 @@ describe("server-runner", () => {
|
|
|
61
66
|
|
|
62
67
|
expect(buildServerConfig()).toEqual({
|
|
63
68
|
appVersion: getCliVersion(import.meta.url),
|
|
69
|
+
update: getUpdateRuntimeInfo(import.meta.url),
|
|
64
70
|
host: "127.0.0.1",
|
|
65
|
-
|
|
71
|
+
stateDir: "/tmp/cs-data",
|
|
66
72
|
auth: {
|
|
67
73
|
enabled: true,
|
|
68
74
|
password: "sekrit",
|
|
@@ -89,6 +95,7 @@ describe("server-runner", () => {
|
|
|
89
95
|
|
|
90
96
|
expect(createServer).toHaveBeenCalledWith({
|
|
91
97
|
appVersion: getCliVersion(import.meta.url),
|
|
98
|
+
update: getUpdateRuntimeInfo(import.meta.url),
|
|
92
99
|
host: "127.0.0.1",
|
|
93
100
|
port: 4173,
|
|
94
101
|
webRoot: "/tmp/web",
|
|
@@ -107,23 +114,47 @@ describe("server-runner", () => {
|
|
|
107
114
|
|
|
108
115
|
it("prepares local state storage using the resolved server config", () => {
|
|
109
116
|
readCliConfig.mockReturnValue({
|
|
110
|
-
|
|
117
|
+
stateDir: "/tmp/cs-data",
|
|
111
118
|
});
|
|
112
119
|
hasWebAssets.mockReturnValue(true);
|
|
113
120
|
getStaticAssetsDir.mockReturnValue("/tmp/web");
|
|
114
121
|
parseServerConfig.mockReturnValue({
|
|
115
|
-
|
|
122
|
+
stateDir: "/tmp/cs-data",
|
|
116
123
|
});
|
|
117
124
|
|
|
118
125
|
prepareLocalStateStorage();
|
|
119
126
|
|
|
120
127
|
expect(parseServerConfig).toHaveBeenCalledWith({
|
|
121
128
|
appVersion: getCliVersion(import.meta.url),
|
|
122
|
-
|
|
129
|
+
update: getUpdateRuntimeInfo(import.meta.url),
|
|
130
|
+
stateDir: "/tmp/cs-data",
|
|
123
131
|
webRoot: "/tmp/web",
|
|
124
132
|
});
|
|
125
133
|
});
|
|
126
134
|
|
|
135
|
+
it("does not create on-disk state for an in-memory stateDir", () => {
|
|
136
|
+
readCliConfig.mockReturnValue({
|
|
137
|
+
stateDir: ":memory:",
|
|
138
|
+
});
|
|
139
|
+
hasWebAssets.mockReturnValue(true);
|
|
140
|
+
getStaticAssetsDir.mockReturnValue("/tmp/web");
|
|
141
|
+
parseServerConfig.mockReturnValue({
|
|
142
|
+
stateDir: ":memory:",
|
|
143
|
+
});
|
|
144
|
+
const originalCwd = process.cwd();
|
|
145
|
+
const testCwd = mkdtempSync(join(tmpdir(), "cs-server-runner-memory-"));
|
|
146
|
+
|
|
147
|
+
try {
|
|
148
|
+
process.chdir(testCwd);
|
|
149
|
+
prepareLocalStateStorage();
|
|
150
|
+
|
|
151
|
+
expect(existsSync(join(testCwd, ":memory:"))).toBe(false);
|
|
152
|
+
} finally {
|
|
153
|
+
process.chdir(originalCwd);
|
|
154
|
+
rmSync(testCwd, { recursive: true, force: true });
|
|
155
|
+
}
|
|
156
|
+
});
|
|
157
|
+
|
|
127
158
|
it("starts the server when executed as the entrypoint", async () => {
|
|
128
159
|
readCliConfig.mockReturnValue(null);
|
|
129
160
|
hasWebAssets.mockReturnValue(true);
|
package/src/server-runner.ts
CHANGED
|
@@ -1,22 +1,23 @@
|
|
|
1
|
-
import type { Server,
|
|
1
|
+
import type { Server, ServerConfigInput } from "@coder-studio/server";
|
|
2
2
|
import { parseServerConfig } from "@coder-studio/server";
|
|
3
3
|
import { mkdirSync } from "fs";
|
|
4
|
-
import { dirname } from "path";
|
|
5
4
|
import { fileURLToPath } from "url";
|
|
6
5
|
import { readCliConfig } from "./config-store.js";
|
|
7
6
|
import { getStaticAssetsDir, hasWebAssets } from "./embed.js";
|
|
8
7
|
import { assertSupportedNodeVersion } from "./node-version.js";
|
|
9
8
|
import { getCliVersion } from "./package-manifest.js";
|
|
9
|
+
import { getUpdateRuntimeInfo } from "./update-runtime.js";
|
|
10
10
|
|
|
11
11
|
const MISSING_WEB_ASSETS_WARNING = "Warning: Web assets not found. Frontend will not be available.";
|
|
12
12
|
|
|
13
|
-
export const buildServerConfig = ():
|
|
13
|
+
export const buildServerConfig = (): ServerConfigInput => {
|
|
14
14
|
const savedConfig = readCliConfig();
|
|
15
|
-
const config:
|
|
15
|
+
const config: ServerConfigInput = {
|
|
16
16
|
appVersion: getCliVersion(import.meta.url),
|
|
17
|
+
update: getUpdateRuntimeInfo(import.meta.url),
|
|
17
18
|
...(savedConfig?.host !== undefined ? { host: savedConfig.host } : {}),
|
|
18
19
|
...(savedConfig?.port !== undefined && savedConfig.port > 0 ? { port: savedConfig.port } : {}),
|
|
19
|
-
...(savedConfig?.
|
|
20
|
+
...(savedConfig?.stateDir !== undefined ? { stateDir: savedConfig.stateDir } : {}),
|
|
20
21
|
...(savedConfig?.password !== undefined
|
|
21
22
|
? {
|
|
22
23
|
auth: {
|
|
@@ -40,8 +41,8 @@ export const buildServerConfig = (): Partial<ServerConfig> => {
|
|
|
40
41
|
|
|
41
42
|
export const prepareLocalStateStorage = (): void => {
|
|
42
43
|
const config = parseServerConfig(buildServerConfig());
|
|
43
|
-
if (config.
|
|
44
|
-
mkdirSync(
|
|
44
|
+
if (config.stateDir !== ":memory:") {
|
|
45
|
+
mkdirSync(config.stateDir, { recursive: true });
|
|
45
46
|
}
|
|
46
47
|
};
|
|
47
48
|
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { getUpdateRuntimeInfo } from "./update-runtime.js";
|
|
3
|
+
|
|
4
|
+
describe("update-runtime", () => {
|
|
5
|
+
it("exposes the package name and restart command contract", () => {
|
|
6
|
+
const runtime = getUpdateRuntimeInfo(import.meta.url);
|
|
7
|
+
|
|
8
|
+
expect(runtime.packageName).toBe("@spencer-kit/coder-studio");
|
|
9
|
+
expect(runtime.cliCommand).toBe("coder-studio");
|
|
10
|
+
expect(runtime.restartArgs).toEqual(["serve", "--restart"]);
|
|
11
|
+
expect(runtime.installArgsPrefix).toEqual(["install", "-g"]);
|
|
12
|
+
});
|
|
13
|
+
});
|