@abelxiaoxing/cadence 2.0.7 → 2.0.8
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 +11 -1
- package/docs/service-management.md +5 -2
- package/package.json +1 -1
- package/src/service-cli.mjs +166 -75
- package/src/service-host.mjs +139 -58
- package/web/dist/assets/cadence.js +1 -1
- package/web/dist/manifest.json +3 -3
package/README.md
CHANGED
|
@@ -24,13 +24,14 @@ They may still report blocked scripts from the pinned Pi SDK's dependencies: `@g
|
|
|
24
24
|
`cadence server install` registers and starts the background service for `~/cadence` on `http://127.0.0.1:17423`; `--workspace /absolute/project` selects another workspace.
|
|
25
25
|
It prints the private link to open, `http://127.0.0.1:17423/?token=…`.
|
|
26
26
|
The port and that token live with the research settings in `~/.pi/agent/cadence/.env`.
|
|
27
|
-
Install creates that file from the research template when missing (
|
|
27
|
+
Install creates that file from the research template when missing (Pi's startup card names the path until it exists), and adds either line when it is missing, never changing an existing one:
|
|
28
28
|
|
|
29
29
|
```sh
|
|
30
30
|
CADENCE_PORT=17423
|
|
31
31
|
CADENCE_TOKEN=<random 43 characters>
|
|
32
32
|
```
|
|
33
33
|
|
|
34
|
+
A file you made yourself works as it is, like Pi's own sources: any permissions (a note suggests `chmod 600` when other users can read it) and dotfiles links; a refusal names the line and reason.
|
|
34
35
|
The token admits anyone who has it to the workspace, so keep the file and the link private; a token you choose needs at least 32 characters.
|
|
35
36
|
To change either, edit the line and run `cadence server restart`, which prints the new link; old links and browser cookies stop working.
|
|
36
37
|
A workspace that an earlier version started with a generated token switches to the file at that restart and keeps its history.
|
|
@@ -375,6 +376,7 @@ Byte values are UTF-8/encoded bounds where specified; they are not targets to si
|
|
|
375
376
|
| Pi's TUI replaces an info status with the next one | The missing-service hint is its own warning notification |
|
|
376
377
|
| Token source the user edits (`CADENCE_TOKEN` in `.env`) | Read once before binding, never bound for the run: an edit cannot fence Stop |
|
|
377
378
|
| Saved port/token source differs from `.env` | Only when stopped, reselect just those under the reservation; running ones refuse |
|
|
379
|
+
| User-made `.env` (0755/0644, umask 002, dotfiles links) | Like Pi's sources: ownership only, links followed; each refusal names its reason |
|
|
378
380
|
|
|
379
381
|
Good: current context → explicit grant → durable acceptance → genuine original native/storage settlement → exact retained dependency → real checks → human review.
|
|
380
382
|
Base: a client leaves and another reattaches to the same accepted run without launching it again.
|
|
@@ -418,6 +420,14 @@ Review the independently enumerated package member list and every emitted body,
|
|
|
418
420
|
Keep public Pi peers external for the extension, the exact Undici/ws runtime edges, the exact optional SDK pins for the standalone owner and all upstream/font/vendor notices.
|
|
419
421
|
Preserve original license bytes through their precise Git attributes; line-ending normalization must not invalidate audited source/notice hashes.
|
|
420
422
|
|
|
423
|
+
### Release 2.0.8 acceptance snapshot
|
|
424
|
+
|
|
425
|
+
Full `verify` passed on the 2.0.8 release tree (`7a40be6` plus the version bump), in the 2.0.3 environment below.
|
|
426
|
+
It ran 1948 root tests in 78 files (27 Windows-only controls skipped on Linux), 111 web tests, 5 real-browser scenarios and the 132-member pack audit, and CI 40201203720 passed 12/12 on the candidate `1c372b2` (this commit before its snapshot).
|
|
427
|
+
The fix answers a 2.0.7 report: `cadence server install` refused existing `.env` files with one message.
|
|
428
|
+
The 2.0.7 refusal was reproduced for a 0755 directory from the older `mkdir -p` docs, 0644 editor files, umask 002 and a symlinked `~/.pi`.
|
|
429
|
+
Those layouts now install, a dotfiles link stays a link, and a remaining refusal (an `export` line) names its line.
|
|
430
|
+
|
|
421
431
|
### Release 2.0.7 acceptance snapshot
|
|
422
432
|
|
|
423
433
|
Full `verify` ran on the 2.0.7 release tree (`7973e23` plus the version bump), in the 2.0.3 environment below.
|
|
@@ -68,9 +68,12 @@ CADENCE_TOKEN=<at least 32 characters, no control or format characters>
|
|
|
68
68
|
```
|
|
69
69
|
|
|
70
70
|
The file keeps the research skills' syntax (`KEY=value` lines, `#` comments, one optional pair of quotes), and each of these keys may appear only once.
|
|
71
|
-
`install` and `restart` create the file from the research template when it is missing, as your own 0600 file
|
|
71
|
+
`install` and `restart` create the file from the research template when it is missing, as your own 0600 file.
|
|
72
72
|
They append a missing `CADENCE_PORT` (17423) or `CADENCE_TOKEN` (random, 43 characters) under a comment, in the file's own line endings, and never change an existing line.
|
|
73
|
-
|
|
73
|
+
It is your own file, made by hand, by older docs (`mkdir -p ~/.pi/agent/cadence`) or by a dotfiles manager, so it is read like Pi's sources: its mode bits are not checked (0644, or 0664 under umask 002, work), links in its path and the file itself are followed, and a rewrite goes to the real file, written 0600, leaving the links in place.
|
|
74
|
+
When other users can read it, `install` and `restart` add a note naming `chmod 600`, without refusing.
|
|
75
|
+
Only a file that cannot be the user's own configuration makes them change nothing: missing or a link to nothing, not a regular file, not owned by you or in a directory you do not own, hard-linked, over 64 KiB or not UTF-8, a line the research skills would also refuse, a duplicate key, a port outside 1-65535 or a short token.
|
|
76
|
+
Each refusal names its reason and line number, never a byte of the file.
|
|
74
77
|
A project's `.pi/cadence/.env`, which the research skills prefer, is never read for the service.
|
|
75
78
|
`start --server-config <.env>` makes these lines authoritative.
|
|
76
79
|
The host reads them once before binding, like an environment token, so editing them while the owner runs changes nothing and can never fence access or the orderly Stop; the next start applies them, and `cadence server restart` does both.
|
package/package.json
CHANGED
package/src/service-cli.mjs
CHANGED
|
@@ -2511,13 +2511,13 @@ var TASK_CONFIG_SCHEMA = "cadence-assistant-checks/1";
|
|
|
2511
2511
|
// src/service/autostart.ts
|
|
2512
2512
|
import { spawnSync as spawnSync2 } from "node:child_process";
|
|
2513
2513
|
import {
|
|
2514
|
-
lstatSync as
|
|
2514
|
+
lstatSync as lstatSync4,
|
|
2515
2515
|
mkdirSync as mkdirSync4,
|
|
2516
2516
|
readFileSync,
|
|
2517
|
-
realpathSync as
|
|
2517
|
+
realpathSync as realpathSync3,
|
|
2518
2518
|
renameSync as renameSync3,
|
|
2519
2519
|
rmSync,
|
|
2520
|
-
statSync,
|
|
2520
|
+
statSync as statSync2,
|
|
2521
2521
|
writeFileSync as writeFileSync3
|
|
2522
2522
|
} from "node:fs";
|
|
2523
2523
|
import { homedir as homedir3 } from "node:os";
|
|
@@ -2533,6 +2533,7 @@ import {
|
|
|
2533
2533
|
|
|
2534
2534
|
// src/service/server-config.ts
|
|
2535
2535
|
import { randomBytes } from "node:crypto";
|
|
2536
|
+
import { lstatSync as lstatSync3, realpathSync as realpathSync2, statSync } from "node:fs";
|
|
2536
2537
|
import { dirname as dirname3, join as join3 } from "node:path";
|
|
2537
2538
|
|
|
2538
2539
|
// skills/_shared/load-config.mjs
|
|
@@ -2832,8 +2833,39 @@ function accessToken(value) {
|
|
|
2832
2833
|
function serverConfigPath(home) {
|
|
2833
2834
|
return join3(home, ".pi", "agent", "cadence", ".env");
|
|
2834
2835
|
}
|
|
2836
|
+
|
|
2837
|
+
class ServerConfigRefusal extends ContractError {
|
|
2838
|
+
path;
|
|
2839
|
+
reason;
|
|
2840
|
+
constructor(path2, reason) {
|
|
2841
|
+
super("corrupt-selected");
|
|
2842
|
+
this.path = path2;
|
|
2843
|
+
this.reason = reason;
|
|
2844
|
+
}
|
|
2845
|
+
}
|
|
2846
|
+
|
|
2847
|
+
class Unusable extends Error {
|
|
2848
|
+
}
|
|
2849
|
+
var unusable = (reason) => {
|
|
2850
|
+
throw new Unusable(reason);
|
|
2851
|
+
};
|
|
2852
|
+
function syntax(error) {
|
|
2853
|
+
const message = error instanceof Error ? error.message : "";
|
|
2854
|
+
const line = /at line (\d+)$/.exec(message)?.[1];
|
|
2855
|
+
if (!line)
|
|
2856
|
+
return "is not in KEY=value lines";
|
|
2857
|
+
if (message.startsWith("Invalid configuration name"))
|
|
2858
|
+
return `has an invalid name on line ${line} (letters, digits and _ only, no "export")`;
|
|
2859
|
+
if (message.startsWith("Unterminated"))
|
|
2860
|
+
return `has an unterminated quote on line ${line}`;
|
|
2861
|
+
return `has no KEY=value setting on line ${line}`;
|
|
2862
|
+
}
|
|
2835
2863
|
function settings(source) {
|
|
2836
|
-
|
|
2864
|
+
try {
|
|
2865
|
+
parseEnvFile(source);
|
|
2866
|
+
} catch (error) {
|
|
2867
|
+
unusable(`${syntax(error)}, which the research skills refuse too`);
|
|
2868
|
+
}
|
|
2837
2869
|
const parts = source.split(/(\r\n|\r|\n)/);
|
|
2838
2870
|
const found = {};
|
|
2839
2871
|
for (let at = 0;at < parts.length; at += 2) {
|
|
@@ -2845,83 +2877,137 @@ function settings(source) {
|
|
|
2845
2877
|
const name = key === PORT_KEY ? "port" : key === TOKEN_KEY ? "token" : undefined;
|
|
2846
2878
|
if (!name)
|
|
2847
2879
|
continue;
|
|
2848
|
-
|
|
2849
|
-
|
|
2880
|
+
const previous = found[name];
|
|
2881
|
+
if (previous)
|
|
2882
|
+
unusable(`sets ${key} twice (lines ${previous.line} and ${at / 2 + 1}); keep one`);
|
|
2850
2883
|
let value = line.slice(separator + 1).trim();
|
|
2851
2884
|
if (value[0] === '"' || value[0] === "'")
|
|
2852
2885
|
value = value.slice(1, -1);
|
|
2853
|
-
found[name] = { at, value };
|
|
2886
|
+
found[name] = { at, line: at / 2 + 1, value };
|
|
2854
2887
|
}
|
|
2855
2888
|
return { parts, ...found };
|
|
2856
2889
|
}
|
|
2857
|
-
function
|
|
2890
|
+
function decode(source) {
|
|
2858
2891
|
const { port, token } = settings(source);
|
|
2859
|
-
if (!port
|
|
2860
|
-
|
|
2892
|
+
if (!port)
|
|
2893
|
+
unusable(`has no ${PORT_KEY} line`);
|
|
2861
2894
|
if (!token)
|
|
2862
|
-
|
|
2863
|
-
|
|
2864
|
-
|
|
2865
|
-
|
|
2866
|
-
|
|
2895
|
+
unusable(`has no ${TOKEN_KEY} line`);
|
|
2896
|
+
const p = port, t = token;
|
|
2897
|
+
const number = Number(p.value);
|
|
2898
|
+
if (!/^[1-9][0-9]{0,4}$/.test(p.value) || number > 65535)
|
|
2899
|
+
unusable(`has ${PORT_KEY} on line ${p.line} that is not a whole number from 1 to 65535`);
|
|
2900
|
+
try {
|
|
2901
|
+
return { port: number, token: accessToken(t.value) };
|
|
2902
|
+
} catch {
|
|
2903
|
+
return unusable(`has ${TOKEN_KEY} on line ${t.line} shorter than 32 characters or with control characters`);
|
|
2904
|
+
}
|
|
2867
2905
|
}
|
|
2868
|
-
|
|
2869
|
-
|
|
2870
|
-
|
|
2871
|
-
|
|
2872
|
-
|
|
2873
|
-
|
|
2906
|
+
var code = (error) => error?.code ?? "error";
|
|
2907
|
+
function locate(path2) {
|
|
2908
|
+
let real;
|
|
2909
|
+
try {
|
|
2910
|
+
real = realpathSync2(path2);
|
|
2911
|
+
} catch (error) {
|
|
2912
|
+
if (code(error) !== "ENOENT")
|
|
2913
|
+
unusable(`cannot be read (${code(error)})`);
|
|
2914
|
+
let link = false;
|
|
2915
|
+
try {
|
|
2916
|
+
link = lstatSync3(path2).isSymbolicLink();
|
|
2917
|
+
} catch {}
|
|
2918
|
+
return unusable(link ? "is a link to a file that does not exist" : "does not exist");
|
|
2919
|
+
}
|
|
2920
|
+
return { real, files: new PrivateFiles(dirname3(real), false) };
|
|
2921
|
+
}
|
|
2922
|
+
function explain(real) {
|
|
2923
|
+
const uid = process.getuid?.();
|
|
2924
|
+
try {
|
|
2925
|
+
const st = lstatSync3(real);
|
|
2926
|
+
if (!st.isFile())
|
|
2927
|
+
return "is not a regular file";
|
|
2928
|
+
if (uid !== undefined && st.uid !== uid)
|
|
2929
|
+
return "is not owned by you";
|
|
2930
|
+
if (uid !== undefined && lstatSync3(dirname3(real)).uid !== uid)
|
|
2931
|
+
return `is in ${dirname3(real)}, which is not owned by you`;
|
|
2932
|
+
if (st.nlink !== 1)
|
|
2933
|
+
return "has more than one hard link";
|
|
2934
|
+
if (st.size > CONFIG_BYTES)
|
|
2935
|
+
return "is larger than 64 KiB";
|
|
2936
|
+
return "changed while Cadence read it; try again";
|
|
2937
|
+
} catch (error) {
|
|
2938
|
+
return `cannot be read (${code(error)})`;
|
|
2874
2939
|
}
|
|
2875
2940
|
}
|
|
2941
|
+
function load(path2) {
|
|
2942
|
+
const { real, files } = locate(path2);
|
|
2943
|
+
let snapshot;
|
|
2944
|
+
try {
|
|
2945
|
+
snapshot = files.read(real, CONFIG_BYTES);
|
|
2946
|
+
} catch {
|
|
2947
|
+
return unusable(explain(real));
|
|
2948
|
+
}
|
|
2949
|
+
if (!snapshot)
|
|
2950
|
+
return unusable("does not exist");
|
|
2951
|
+
let source;
|
|
2952
|
+
try {
|
|
2953
|
+
source = decoded(snapshot);
|
|
2954
|
+
} catch {
|
|
2955
|
+
return unusable("is not UTF-8 text");
|
|
2956
|
+
}
|
|
2957
|
+
return { path: path2, real, files, snapshot, source };
|
|
2958
|
+
}
|
|
2876
2959
|
function decoded(snapshot) {
|
|
2877
2960
|
return new TextDecoder("utf-8", { fatal: true }).decode(snapshot.bytes);
|
|
2878
2961
|
}
|
|
2879
|
-
function
|
|
2962
|
+
function refusing(path2, read) {
|
|
2880
2963
|
try {
|
|
2881
|
-
|
|
2882
|
-
|
|
2883
|
-
if (
|
|
2884
|
-
throw
|
|
2885
|
-
|
|
2886
|
-
path: path2,
|
|
2887
|
-
config: decodeServerConfig(decoded(snapshot)),
|
|
2888
|
-
files,
|
|
2889
|
-
snapshot
|
|
2890
|
-
};
|
|
2891
|
-
} catch {
|
|
2892
|
-
throw new ServerConfigRefusal(path2);
|
|
2964
|
+
return read();
|
|
2965
|
+
} catch (error) {
|
|
2966
|
+
if (error instanceof ServerConfigRefusal)
|
|
2967
|
+
throw error;
|
|
2968
|
+
throw new ServerConfigRefusal(path2, error instanceof Unusable ? error.message : `cannot be read (${code(error)})`);
|
|
2893
2969
|
}
|
|
2894
2970
|
}
|
|
2971
|
+
function readServerConfig(path2) {
|
|
2972
|
+
return refusing(path2, () => {
|
|
2973
|
+
const file = load(path2);
|
|
2974
|
+
return { ...file, config: decode(file.source) };
|
|
2975
|
+
});
|
|
2976
|
+
}
|
|
2895
2977
|
function ensureServerConfig(home) {
|
|
2896
2978
|
const path2 = serverConfigPath(home);
|
|
2897
|
-
const added =
|
|
2898
|
-
|
|
2899
|
-
|
|
2900
|
-
|
|
2901
|
-
|
|
2902
|
-
|
|
2903
|
-
|
|
2904
|
-
const
|
|
2905
|
-
const found = settings(source);
|
|
2979
|
+
const added = refusing(path2, () => {
|
|
2980
|
+
try {
|
|
2981
|
+
ensureUserConfig(home);
|
|
2982
|
+
} catch (error) {
|
|
2983
|
+
unusable(`could not be created (${code(error)})`);
|
|
2984
|
+
}
|
|
2985
|
+
const file2 = load(path2);
|
|
2986
|
+
const found = settings(file2.source);
|
|
2906
2987
|
const lines = [];
|
|
2907
2988
|
if (!found.port)
|
|
2908
2989
|
lines.push(`${PORT_KEY}=${DEFAULT_PORT}`);
|
|
2909
2990
|
if (!found.token)
|
|
2910
2991
|
lines.push(`${TOKEN_KEY}=${randomBytes(32).toString("base64url")}`);
|
|
2911
2992
|
if (lines.length) {
|
|
2993
|
+
const { source } = file2;
|
|
2912
2994
|
const eol = source.includes(`\r
|
|
2913
2995
|
`) ? `\r
|
|
2914
2996
|
` : `
|
|
2915
2997
|
`;
|
|
2916
2998
|
const ending = !source || /[\r\n]$/.test(source) ? "" : eol;
|
|
2917
2999
|
const gap = source ? eol : "";
|
|
2918
|
-
|
|
2919
|
-
|
|
3000
|
+
try {
|
|
3001
|
+
file2.files.replace(file2.real, file2.snapshot, `${source}${ending}${gap}${[...HEADER, ...lines].join(eol)}${eol}`, CONFIG_BYTES);
|
|
3002
|
+
} catch (error) {
|
|
3003
|
+
unusable(`could not be updated (${code(error)})`);
|
|
3004
|
+
}
|
|
2920
3005
|
}
|
|
2921
|
-
|
|
2922
|
-
|
|
2923
|
-
|
|
2924
|
-
|
|
3006
|
+
return lines.map((line) => line.slice(0, line.indexOf("=")));
|
|
3007
|
+
});
|
|
3008
|
+
const file = readServerConfig(path2);
|
|
3009
|
+
const exposed = process.platform !== "win32" && (statSync(file.real).mode & 63) !== 0;
|
|
3010
|
+
return { ...file, added, exposed };
|
|
2925
3011
|
}
|
|
2926
3012
|
|
|
2927
3013
|
// src/service/autostart.ts
|
|
@@ -3000,7 +3086,7 @@ function agentDirectory(s) {
|
|
|
3000
3086
|
function piUserInstall(s) {
|
|
3001
3087
|
try {
|
|
3002
3088
|
const managed = join4(agentDirectory(s), "npm", "node_modules", "@abelxiaoxing", "cadence");
|
|
3003
|
-
return
|
|
3089
|
+
return realpathSync3(managed) === realpathSync3(s.root);
|
|
3004
3090
|
} catch {
|
|
3005
3091
|
return false;
|
|
3006
3092
|
}
|
|
@@ -3008,7 +3094,7 @@ function piUserInstall(s) {
|
|
|
3008
3094
|
function sdkPackages(root) {
|
|
3009
3095
|
let start;
|
|
3010
3096
|
try {
|
|
3011
|
-
start =
|
|
3097
|
+
start = realpathSync3(join4(root, "src"));
|
|
3012
3098
|
} catch {
|
|
3013
3099
|
return;
|
|
3014
3100
|
}
|
|
@@ -3089,7 +3175,7 @@ function buildUnit(u) {
|
|
|
3089
3175
|
}
|
|
3090
3176
|
function readGenerated(path2, marker) {
|
|
3091
3177
|
try {
|
|
3092
|
-
if (!
|
|
3178
|
+
if (!lstatSync4(path2).isFile())
|
|
3093
3179
|
return null;
|
|
3094
3180
|
const bytes = readFileSync(path2);
|
|
3095
3181
|
const text2 = bytes[0] === 255 && bytes[1] === 254 ? bytes.subarray(2).toString("utf16le") : bytes.toString("utf8");
|
|
@@ -3105,7 +3191,7 @@ function unitWorkspace(text2) {
|
|
|
3105
3191
|
}
|
|
3106
3192
|
function directory(path2) {
|
|
3107
3193
|
try {
|
|
3108
|
-
return isAbsolute3(path2) &&
|
|
3194
|
+
return isAbsolute3(path2) && statSync2(path2).isDirectory();
|
|
3109
3195
|
} catch {
|
|
3110
3196
|
return false;
|
|
3111
3197
|
}
|
|
@@ -3140,7 +3226,7 @@ function write(path2, content) {
|
|
|
3140
3226
|
}
|
|
3141
3227
|
function regularFile(path2) {
|
|
3142
3228
|
try {
|
|
3143
|
-
return
|
|
3229
|
+
return statSync2(path2).isFile();
|
|
3144
3230
|
} catch {
|
|
3145
3231
|
return false;
|
|
3146
3232
|
}
|
|
@@ -3270,7 +3356,7 @@ function autostartStatus(s) {
|
|
|
3270
3356
|
|
|
3271
3357
|
// src/service/coordination.ts
|
|
3272
3358
|
import { randomBytes as randomBytes2, randomUUID as randomUUID3 } from "node:crypto";
|
|
3273
|
-
import { opendirSync, realpathSync as
|
|
3359
|
+
import { opendirSync, realpathSync as realpathSync4 } from "node:fs";
|
|
3274
3360
|
import { hostname as hostname2 } from "node:os";
|
|
3275
3361
|
import { dirname as dirname5, isAbsolute as isAbsolute4, join as join5, relative as relative3, sep as sep4 } from "node:path";
|
|
3276
3362
|
var registrationKeys = [
|
|
@@ -3428,7 +3514,7 @@ class ServiceCoordination {
|
|
|
3428
3514
|
const o = closed(json(snapshot, REGISTRY_BYTES), ["schema", "entries"]);
|
|
3429
3515
|
const entries = list(o.entries, REGISTRY_ENTRIES, decodeReservation);
|
|
3430
3516
|
for (const [i, row] of entries.entries()) {
|
|
3431
|
-
if (row.stateHome !== this.stateHome ||
|
|
3517
|
+
if (row.stateHome !== this.stateHome || realpathSync4(row.root) !== row.root)
|
|
3432
3518
|
refused();
|
|
3433
3519
|
if (entries.slice(0, i).some((other) => rootsOverlap(row.root, other.root) || row.endpoint === other.endpoint))
|
|
3434
3520
|
refused();
|
|
@@ -4167,7 +4253,7 @@ import { isatty } from "node:tty";
|
|
|
4167
4253
|
|
|
4168
4254
|
// src/history/recovery.ts
|
|
4169
4255
|
import { createHash as createHash5, randomUUID as randomUUID5 } from "node:crypto";
|
|
4170
|
-
import { lstatSync as
|
|
4256
|
+
import { lstatSync as lstatSync7, opendirSync as opendirSync3 } from "node:fs";
|
|
4171
4257
|
import { join as join9, relative as relative4 } from "node:path";
|
|
4172
4258
|
|
|
4173
4259
|
// src/service/recovery.ts
|
|
@@ -4222,7 +4308,7 @@ import {
|
|
|
4222
4308
|
constants as constants5,
|
|
4223
4309
|
fstatSync as fstatSync4,
|
|
4224
4310
|
fsyncSync as fsyncSync3,
|
|
4225
|
-
lstatSync as
|
|
4311
|
+
lstatSync as lstatSync6,
|
|
4226
4312
|
opendirSync as opendirSync2,
|
|
4227
4313
|
openSync as openSync5,
|
|
4228
4314
|
statfsSync
|
|
@@ -4252,7 +4338,7 @@ import {
|
|
|
4252
4338
|
closeSync as closeSync4,
|
|
4253
4339
|
constants as constants4,
|
|
4254
4340
|
fstatSync as fstatSync3,
|
|
4255
|
-
lstatSync as
|
|
4341
|
+
lstatSync as lstatSync5,
|
|
4256
4342
|
openSync as openSync4,
|
|
4257
4343
|
readSync as readSync3
|
|
4258
4344
|
} from "node:fs";
|
|
@@ -4264,7 +4350,7 @@ var stamp2 = (s) => [s.dev, s.ino, s.size, s.mode, s.uid, s.nlink, s.ctimeMs, s.
|
|
|
4264
4350
|
function selected(files, path2, read, walks) {
|
|
4265
4351
|
if (!files.files.directory(dirname6(path2), false, walks))
|
|
4266
4352
|
bad();
|
|
4267
|
-
const before =
|
|
4353
|
+
const before = lstatSync5(path2);
|
|
4268
4354
|
if (!before.isFile() || before.nlink !== 1 || process.platform !== "win32" && (before.mode & 63 || process.getuid && before.uid !== process.getuid()))
|
|
4269
4355
|
bad();
|
|
4270
4356
|
const fd = openSync4(path2, constants4.O_RDONLY | (constants4.O_NOFOLLOW ?? 0));
|
|
@@ -4272,7 +4358,7 @@ function selected(files, path2, read, walks) {
|
|
|
4272
4358
|
if (stamp2(fstatSync3(fd)) !== stamp2(before))
|
|
4273
4359
|
bad();
|
|
4274
4360
|
const result = read(fd, before.size, stamp2(before));
|
|
4275
|
-
if (stamp2(fstatSync3(fd)) !== stamp2(before) || stamp2(
|
|
4361
|
+
if (stamp2(fstatSync3(fd)) !== stamp2(before) || stamp2(lstatSync5(path2)) !== stamp2(before))
|
|
4276
4362
|
bad();
|
|
4277
4363
|
return result;
|
|
4278
4364
|
} finally {
|
|
@@ -4900,7 +4986,7 @@ class HistoryFiles {
|
|
|
4900
4986
|
return this.files.read(path2, maximum);
|
|
4901
4987
|
let size;
|
|
4902
4988
|
try {
|
|
4903
|
-
size =
|
|
4989
|
+
size = lstatSync6(path2).size;
|
|
4904
4990
|
} catch {
|
|
4905
4991
|
return this.files.read(path2, 0, budget.walks);
|
|
4906
4992
|
}
|
|
@@ -4988,7 +5074,7 @@ class HistoryFiles {
|
|
|
4988
5074
|
const dir = opendirSync2(path2);
|
|
4989
5075
|
try {
|
|
4990
5076
|
for (let e = dir.readSync();e; e = dir.readSync()) {
|
|
4991
|
-
const p = join8(path2, e.name), st =
|
|
5077
|
+
const p = join8(path2, e.name), st = lstatSync6(p);
|
|
4992
5078
|
if (st.isDirectory())
|
|
4993
5079
|
bytes += walk(p, depth + 1);
|
|
4994
5080
|
else {
|
|
@@ -5188,7 +5274,7 @@ class HistoryFiles {
|
|
|
5188
5274
|
let committed;
|
|
5189
5275
|
const fd = await open(path2, constants5.O_WRONLY | constants5.O_APPEND | (constants5.O_NOFOLLOW ?? 0));
|
|
5190
5276
|
const assertOriginal = (bytes) => {
|
|
5191
|
-
const original = fstatSync4(fd.fd), selected2 =
|
|
5277
|
+
const original = fstatSync4(fd.fd), selected2 = lstatSync6(path2);
|
|
5192
5278
|
if (original.dev !== selected2.dev || original.ino !== selected2.ino || original.nlink !== 1)
|
|
5193
5279
|
corrupt();
|
|
5194
5280
|
const snapshot = this.files.read(path2, LIMITS.segmentBytes);
|
|
@@ -5305,7 +5391,7 @@ class HistoryFiles {
|
|
|
5305
5391
|
try {
|
|
5306
5392
|
lease.assert();
|
|
5307
5393
|
this.files.assert(path2, before, LIMITS.segmentBytes);
|
|
5308
|
-
const opened = await fd.stat(), selected2 =
|
|
5394
|
+
const opened = await fd.stat(), selected2 = lstatSync6(path2);
|
|
5309
5395
|
if (opened.ino !== selected2.ino || opened.dev !== selected2.dev || opened.nlink !== 1)
|
|
5310
5396
|
corrupt();
|
|
5311
5397
|
beforeEffect?.();
|
|
@@ -5753,7 +5839,7 @@ function digestHistory(files, exclude) {
|
|
|
5753
5839
|
const walk = (path2, depth) => {
|
|
5754
5840
|
if (depth > 8 || !files.files.directory(path2))
|
|
5755
5841
|
throw new ContractError("corrupt-selected");
|
|
5756
|
-
const directory2 =
|
|
5842
|
+
const directory2 = lstatSync7(path2);
|
|
5757
5843
|
digest.update(`incarnation:${directory2.dev}:${directory2.ino}
|
|
5758
5844
|
`);
|
|
5759
5845
|
const dir = opendirSync3(path2), names2 = [];
|
|
@@ -5771,7 +5857,7 @@ function digestHistory(files, exclude) {
|
|
|
5771
5857
|
if (selected2 === exclude)
|
|
5772
5858
|
continue;
|
|
5773
5859
|
const rel = relative4(files.directory, selected2);
|
|
5774
|
-
if (
|
|
5860
|
+
if (lstatSync7(selected2).isDirectory()) {
|
|
5775
5861
|
digest.update(`directory:${Buffer.byteLength(rel)}:${rel}
|
|
5776
5862
|
`);
|
|
5777
5863
|
walk(selected2, depth + 1);
|
|
@@ -6395,8 +6481,9 @@ async function stopService(c) {
|
|
|
6395
6481
|
function failedStatus(result) {
|
|
6396
6482
|
return !!(result && typeof result === "object" && ("status" in result) && ["unconfirmed", "unavailable", "quarantined", "refused"].includes(String(result.status)));
|
|
6397
6483
|
}
|
|
6398
|
-
function serverConfigHint(
|
|
6399
|
-
return `Cadence changed nothing: ${
|
|
6484
|
+
function serverConfigHint(error) {
|
|
6485
|
+
return `Cadence changed nothing: ${error.path} ${error.reason}.
|
|
6486
|
+
Fix it and run the command again; the file holds the WebUI port and token as ${PORT_KEY}=<1-65535> and ${TOKEN_KEY}=<at least 32 characters>, one line each.
|
|
6400
6487
|
`;
|
|
6401
6488
|
}
|
|
6402
6489
|
function startupGuidance(result, input) {
|
|
@@ -6438,7 +6525,7 @@ async function startService(options, foreground, hostUrl) {
|
|
|
6438
6525
|
input = decodeHostInput(selectStart(options, process.env));
|
|
6439
6526
|
} catch (error) {
|
|
6440
6527
|
if (error instanceof ServerConfigRefusal)
|
|
6441
|
-
process.stderr.write(serverConfigHint(error
|
|
6528
|
+
process.stderr.write(serverConfigHint(error));
|
|
6442
6529
|
throw error;
|
|
6443
6530
|
}
|
|
6444
6531
|
const serialized = `${JSON.stringify(input)}
|
|
@@ -6466,9 +6553,9 @@ async function startService(options, foreground, hostUrl) {
|
|
|
6466
6553
|
stdio: ["pipe", "pipe", "ignore"]
|
|
6467
6554
|
});
|
|
6468
6555
|
let completed = false;
|
|
6469
|
-
const closure = new Promise((resolve6) => child.once("close", (
|
|
6556
|
+
const closure = new Promise((resolve6) => child.once("close", (code2, signal) => {
|
|
6470
6557
|
completed = true;
|
|
6471
|
-
if (foreground && (
|
|
6558
|
+
if (foreground && (code2 !== 0 || signal !== null))
|
|
6472
6559
|
process.exitCode = 1;
|
|
6473
6560
|
resolve6();
|
|
6474
6561
|
}));
|
|
@@ -6752,13 +6839,17 @@ async function serverMain(argv, hostUrl) {
|
|
|
6752
6839
|
process.stderr.write(researchTemplate(s.home));
|
|
6753
6840
|
if (action === "install" || action === "restart") {
|
|
6754
6841
|
try {
|
|
6755
|
-
const { added } = ensureServerConfig(s.home);
|
|
6842
|
+
const { added, exposed } = ensureServerConfig(s.home);
|
|
6843
|
+
const path2 = serverConfigPath(s.home);
|
|
6756
6844
|
if (added.length)
|
|
6757
|
-
process.stderr.write(`Cadence added ${added.map((key) => key === PORT_KEY ? `${key}=${DEFAULT_PORT}` : `a random private ${key}`).join(" and ")} to ${
|
|
6845
|
+
process.stderr.write(`Cadence added ${added.map((key) => key === PORT_KEY ? `${key}=${DEFAULT_PORT}` : `a random private ${key}`).join(" and ")} to ${path2}
|
|
6846
|
+
`);
|
|
6847
|
+
if (exposed)
|
|
6848
|
+
process.stderr.write(`Note: other users can read ${path2}; chmod 600 ${JSON.stringify(path2)} keeps ${TOKEN_KEY} private.
|
|
6758
6849
|
`);
|
|
6759
6850
|
} catch (error) {
|
|
6760
6851
|
if (error instanceof ServerConfigRefusal)
|
|
6761
|
-
process.stderr.write(serverConfigHint(error
|
|
6852
|
+
process.stderr.write(serverConfigHint(error));
|
|
6762
6853
|
throw error;
|
|
6763
6854
|
}
|
|
6764
6855
|
}
|