@xfey/tutti 0.1.61 → 0.1.63
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 +2 -2
- package/dist/server-shell/cli/args.d.ts +4 -0
- package/dist/server-shell/cli/args.js +11 -0
- package/dist/server-shell/cli/cli.js +35 -11
- package/dist/server-shell/cli/host-server-runtime.d.ts +2 -0
- package/dist/server-shell/cli/host-server-runtime.js +12 -5
- package/dist/server-shell/cli/local-control-client.d.ts +5 -1
- package/dist/server-shell/cli/local-control-client.js +19 -0
- package/dist/server-shell/desktop-integration/manager.d.ts +2 -0
- package/dist/server-shell/desktop-integration/manager.js +19 -31
- package/dist/server-shell/http/routes/local-control.d.ts +2 -0
- package/dist/server-shell/http/routes/local-control.js +24 -0
- package/dist/server-shell/local-console/invocation-context.js +10 -1
- package/dist/server-shell/local-console/operation-coordinator.d.ts +16 -0
- package/dist/server-shell/local-console/operation-coordinator.js +50 -0
- package/dist/server-shell/local-console/package-update-completion.d.ts +25 -0
- package/dist/server-shell/local-console/package-update-completion.js +91 -0
- package/dist/server-shell/local-console/package-update-hosts.d.ts +17 -0
- package/dist/server-shell/local-console/package-update-hosts.js +92 -0
- package/dist/server-shell/local-console/package-update-lock.d.ts +46 -0
- package/dist/server-shell/local-console/package-update-lock.js +223 -0
- package/dist/server-shell/local-console/package-update-log.d.ts +20 -0
- package/dist/server-shell/local-console/package-update-log.js +75 -0
- package/dist/server-shell/local-console/package-update-process.d.ts +22 -0
- package/dist/server-shell/local-console/package-update-process.js +252 -0
- package/dist/server-shell/local-console/package-update-record.d.ts +53 -0
- package/dist/server-shell/local-console/package-update-record.js +180 -0
- package/dist/server-shell/local-console/package-update-recovery.d.ts +34 -0
- package/dist/server-shell/local-console/package-update-recovery.js +140 -0
- package/dist/server-shell/local-console/package-update-service.d.ts +72 -0
- package/dist/server-shell/local-console/package-update-service.js +400 -0
- package/dist/server-shell/local-console/package-update-worker.d.ts +55 -0
- package/dist/server-shell/local-console/package-update-worker.js +297 -0
- package/dist/server-shell/local-console/project-service.d.ts +2 -0
- package/dist/server-shell/local-console/project-service.js +16 -12
- package/dist/server-shell/local-console/server.d.ts +9 -0
- package/dist/server-shell/local-console/server.js +92 -4
- package/dist/server-shell/package-installation/command.d.ts +36 -0
- package/dist/server-shell/package-installation/command.js +162 -0
- package/dist/server-shell/package-installation/discovery.d.ts +51 -0
- package/dist/server-shell/package-installation/discovery.js +137 -0
- package/dist/server-shell/package-installation/index.d.ts +5 -0
- package/dist/server-shell/package-installation/index.js +5 -0
- package/dist/server-shell/package-installation/installation.d.ts +34 -0
- package/dist/server-shell/package-installation/installation.js +123 -0
- package/dist/server-shell/package-installation/semver.d.ts +9 -0
- package/dist/server-shell/package-installation/semver.js +84 -0
- package/package.json +1 -1
- package/web/assets/{homepage-motion-scene-CY7o4hnR.js → homepage-motion-scene-CRmrkkEH.js} +1 -1
- package/web/assets/{index-B08r3x8o.js → index-C9JrplHV.js} +14 -14
- package/web/assets/{index-DpabiRgp.css → index-IZKcmU_g.css} +1 -1
- package/web/index.html +2 -2
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { inspectMachineProject, } from "../cli/machine-project-inspector.js";
|
|
2
|
+
import { readHostLocalPackageUpdateReadiness, requestHostLocalIdleShutdown, } from "../cli/local-control-client.js";
|
|
3
|
+
import { createRuntimeEndpointProbe, waitForHostShutdown } from "../cli/host-runtime-endpoint.js";
|
|
4
|
+
import { spawnDetachedHost, waitForManagedHostReady } from "../cli/managed-host.js";
|
|
5
|
+
import { resolveManagedProjectContext } from "../cli/project-resolver.js";
|
|
6
|
+
const PACKAGE_UPDATE_HOST_SHUTDOWN_WAIT_MS = 5_000;
|
|
7
|
+
export async function stopPackageUpdateHost(options) {
|
|
8
|
+
const fetchImpl = options.fetchImpl ?? fetch;
|
|
9
|
+
let inspection;
|
|
10
|
+
try {
|
|
11
|
+
inspection = await inspectMachineProject(options.projectId, {
|
|
12
|
+
tuttiHome: options.tuttiHome,
|
|
13
|
+
fetchImpl,
|
|
14
|
+
});
|
|
15
|
+
}
|
|
16
|
+
catch {
|
|
17
|
+
return "host_identity_unverified";
|
|
18
|
+
}
|
|
19
|
+
if (inspection.host.kind !== "running" ||
|
|
20
|
+
!inspection.host.identity_verified ||
|
|
21
|
+
inspection.endpoint.kind !== "valid") {
|
|
22
|
+
return "host_identity_unverified";
|
|
23
|
+
}
|
|
24
|
+
const endpoint = inspection.endpoint.record;
|
|
25
|
+
try {
|
|
26
|
+
const readiness = await readHostLocalPackageUpdateReadiness({ endpoint, fetchImpl });
|
|
27
|
+
if (readiness === "busy") {
|
|
28
|
+
return "host_busy";
|
|
29
|
+
}
|
|
30
|
+
if (readiness === "unsupported") {
|
|
31
|
+
return "host_update_unsupported";
|
|
32
|
+
}
|
|
33
|
+
const disposition = await requestHostLocalIdleShutdown({ endpoint, fetchImpl });
|
|
34
|
+
if (disposition === "busy") {
|
|
35
|
+
return "host_busy";
|
|
36
|
+
}
|
|
37
|
+
if (disposition !== "accepted") {
|
|
38
|
+
return "host_update_unsupported";
|
|
39
|
+
}
|
|
40
|
+
await waitForHostShutdown({
|
|
41
|
+
endpoint,
|
|
42
|
+
probeRuntimeEndpoint: createRuntimeEndpointProbe(fetchImpl),
|
|
43
|
+
shutdownWaitMs: PACKAGE_UPDATE_HOST_SHUTDOWN_WAIT_MS,
|
|
44
|
+
});
|
|
45
|
+
return "stopped";
|
|
46
|
+
}
|
|
47
|
+
catch {
|
|
48
|
+
return "host_identity_unverified";
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
export async function restorePackageUpdateHosts(options) {
|
|
52
|
+
const restored = [];
|
|
53
|
+
const failed = [];
|
|
54
|
+
for (const projectId of [...new Set(options.projectIds)].sort()) {
|
|
55
|
+
try {
|
|
56
|
+
const inspection = await inspectMachineProject(projectId, {
|
|
57
|
+
tuttiHome: options.tuttiHome,
|
|
58
|
+
...(options.fetchImpl === undefined ? {} : { fetchImpl: options.fetchImpl }),
|
|
59
|
+
});
|
|
60
|
+
if (inspection.host.kind === "running" && inspection.host.identity_verified) {
|
|
61
|
+
restored.push(projectId);
|
|
62
|
+
continue;
|
|
63
|
+
}
|
|
64
|
+
const project = resolveManagedProjectContext({
|
|
65
|
+
target: projectId,
|
|
66
|
+
env: { ...options.env, TUTTI_HOME: options.tuttiHome },
|
|
67
|
+
});
|
|
68
|
+
if (!project.workspace_exists || project.provider_config.status !== "configured") {
|
|
69
|
+
failed.push(projectId);
|
|
70
|
+
continue;
|
|
71
|
+
}
|
|
72
|
+
spawnDetachedHost({
|
|
73
|
+
workspaceRoot: project.workspace_root,
|
|
74
|
+
env: { ...options.env, TUTTI_HOME: options.tuttiHome },
|
|
75
|
+
inheritProcessEnv: false,
|
|
76
|
+
});
|
|
77
|
+
await waitForManagedHostReady({
|
|
78
|
+
tuttiHome: options.tuttiHome,
|
|
79
|
+
projectId,
|
|
80
|
+
workspaceRoot: project.workspace_root,
|
|
81
|
+
inviteMode: "preserve",
|
|
82
|
+
...(options.fetchImpl === undefined ? {} : { fetchImpl: options.fetchImpl }),
|
|
83
|
+
});
|
|
84
|
+
restored.push(projectId);
|
|
85
|
+
}
|
|
86
|
+
catch {
|
|
87
|
+
failed.push(projectId);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
return { restored_project_ids: restored, failed_project_ids: failed };
|
|
91
|
+
}
|
|
92
|
+
//# sourceMappingURL=package-update-hosts.js.map
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
export type PackageUpdateLockInspection = {
|
|
2
|
+
status: "unlocked";
|
|
3
|
+
} | {
|
|
4
|
+
status: "active";
|
|
5
|
+
} | {
|
|
6
|
+
status: "recovery_required";
|
|
7
|
+
};
|
|
8
|
+
export type PackageUpdateLockLease = {
|
|
9
|
+
path: string;
|
|
10
|
+
transferTo: (pid: number) => void;
|
|
11
|
+
release: () => void;
|
|
12
|
+
};
|
|
13
|
+
export type PackageUpdateLockAcquisition = {
|
|
14
|
+
status: "acquired";
|
|
15
|
+
lease: PackageUpdateLockLease;
|
|
16
|
+
} | {
|
|
17
|
+
status: "active" | "recovery_required";
|
|
18
|
+
};
|
|
19
|
+
export declare function packageUpdateLockPath(tuttiHome: string): string;
|
|
20
|
+
export declare function inspectPackageUpdateLock(options: {
|
|
21
|
+
tuttiHome: string;
|
|
22
|
+
now?: () => number;
|
|
23
|
+
staleMs?: number;
|
|
24
|
+
processAlive?: (pid: number) => boolean;
|
|
25
|
+
}): PackageUpdateLockInspection;
|
|
26
|
+
export declare function tryAcquirePackageUpdateLock(options: {
|
|
27
|
+
tuttiHome: string;
|
|
28
|
+
now?: () => number;
|
|
29
|
+
pid?: number;
|
|
30
|
+
staleMs?: number;
|
|
31
|
+
processAlive?: (pid: number) => boolean;
|
|
32
|
+
}): PackageUpdateLockAcquisition;
|
|
33
|
+
export declare function removeRecoverablePackageUpdateLock(options: {
|
|
34
|
+
tuttiHome: string;
|
|
35
|
+
now?: () => number;
|
|
36
|
+
staleMs?: number;
|
|
37
|
+
processAlive?: (pid: number) => boolean;
|
|
38
|
+
}): boolean;
|
|
39
|
+
export declare function transferPackageUpdateLockFromCurrentProcess(options: {
|
|
40
|
+
tuttiHome: string;
|
|
41
|
+
nextPid: number;
|
|
42
|
+
now?: () => number;
|
|
43
|
+
}): boolean;
|
|
44
|
+
export declare function releasePackageUpdateLockForCurrentProcess(tuttiHome: string): boolean;
|
|
45
|
+
export declare function releasePackageUpdateLockForExpectedPid(tuttiHome: string, expectedPid: number): boolean;
|
|
46
|
+
//# sourceMappingURL=package-update-lock.d.ts.map
|
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
import { randomBytes } from "node:crypto";
|
|
2
|
+
import { chmodSync, closeSync, constants, mkdirSync, openSync, readFileSync, renameSync, statSync, unlinkSync, writeFileSync, } from "node:fs";
|
|
3
|
+
import { dirname, join } from "node:path";
|
|
4
|
+
const DEFAULT_STALE_MS = 30_000;
|
|
5
|
+
export function packageUpdateLockPath(tuttiHome) {
|
|
6
|
+
return join(tuttiHome, "runtime", "package-update.lock");
|
|
7
|
+
}
|
|
8
|
+
function isPositiveInteger(value) {
|
|
9
|
+
return Number.isInteger(value) && value > 0;
|
|
10
|
+
}
|
|
11
|
+
function parseRecord(path) {
|
|
12
|
+
try {
|
|
13
|
+
const value = JSON.parse(readFileSync(path, "utf8"));
|
|
14
|
+
if (Object.keys(value).sort().join("\n") !==
|
|
15
|
+
["acquired_at", "owner_id", "pid", "schema_version", "updated_at"].join("\n") ||
|
|
16
|
+
value.schema_version !== 1 ||
|
|
17
|
+
!isPositiveInteger(value.pid) ||
|
|
18
|
+
typeof value.owner_id !== "string" ||
|
|
19
|
+
value.owner_id === "" ||
|
|
20
|
+
typeof value.acquired_at !== "string" ||
|
|
21
|
+
typeof value.updated_at !== "string" ||
|
|
22
|
+
!Number.isFinite(Date.parse(value.acquired_at)) ||
|
|
23
|
+
!Number.isFinite(Date.parse(value.updated_at))) {
|
|
24
|
+
return null;
|
|
25
|
+
}
|
|
26
|
+
return value;
|
|
27
|
+
}
|
|
28
|
+
catch {
|
|
29
|
+
return null;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
function defaultProcessAlive(pid) {
|
|
33
|
+
try {
|
|
34
|
+
process.kill(pid, 0);
|
|
35
|
+
return true;
|
|
36
|
+
}
|
|
37
|
+
catch (error) {
|
|
38
|
+
return (typeof error === "object" &&
|
|
39
|
+
error !== null &&
|
|
40
|
+
"code" in error &&
|
|
41
|
+
error.code === "EPERM");
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
function fileAge(path, now) {
|
|
45
|
+
try {
|
|
46
|
+
return Math.max(0, now() - statSync(path).mtimeMs);
|
|
47
|
+
}
|
|
48
|
+
catch {
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
export function inspectPackageUpdateLock(options) {
|
|
53
|
+
const path = packageUpdateLockPath(options.tuttiHome);
|
|
54
|
+
const age = fileAge(path, options.now ?? Date.now);
|
|
55
|
+
if (age === null) {
|
|
56
|
+
return { status: "unlocked" };
|
|
57
|
+
}
|
|
58
|
+
const record = parseRecord(path);
|
|
59
|
+
if (record === null) {
|
|
60
|
+
return { status: "recovery_required" };
|
|
61
|
+
}
|
|
62
|
+
if ((options.processAlive ?? defaultProcessAlive)(record.pid)) {
|
|
63
|
+
return { status: "active" };
|
|
64
|
+
}
|
|
65
|
+
return age < (options.staleMs ?? DEFAULT_STALE_MS)
|
|
66
|
+
? { status: "active" }
|
|
67
|
+
: { status: "recovery_required" };
|
|
68
|
+
}
|
|
69
|
+
function ensurePrivateRuntimeDirectory(path) {
|
|
70
|
+
const directory = dirname(path);
|
|
71
|
+
mkdirSync(directory, { recursive: true, mode: 0o700 });
|
|
72
|
+
chmodSync(directory, 0o700);
|
|
73
|
+
}
|
|
74
|
+
function writeRecord(path, record) {
|
|
75
|
+
const temporaryPath = `${path}.${record.owner_id}.tmp`;
|
|
76
|
+
try {
|
|
77
|
+
writeFileSync(temporaryPath, `${JSON.stringify(record)}\n`, { encoding: "utf8", mode: 0o600 });
|
|
78
|
+
chmodSync(temporaryPath, 0o600);
|
|
79
|
+
renameSync(temporaryPath, path);
|
|
80
|
+
chmodSync(path, 0o600);
|
|
81
|
+
}
|
|
82
|
+
finally {
|
|
83
|
+
try {
|
|
84
|
+
unlinkSync(temporaryPath);
|
|
85
|
+
}
|
|
86
|
+
catch {
|
|
87
|
+
// Atomic rename normally removes the temporary path.
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
export function tryAcquirePackageUpdateLock(options) {
|
|
92
|
+
const path = packageUpdateLockPath(options.tuttiHome);
|
|
93
|
+
const now = options.now ?? Date.now;
|
|
94
|
+
ensurePrivateRuntimeDirectory(path);
|
|
95
|
+
const ownerId = randomBytes(16).toString("base64url");
|
|
96
|
+
const timestamp = new Date(now()).toISOString();
|
|
97
|
+
const record = {
|
|
98
|
+
schema_version: 1,
|
|
99
|
+
pid: options.pid ?? process.pid,
|
|
100
|
+
owner_id: ownerId,
|
|
101
|
+
acquired_at: timestamp,
|
|
102
|
+
updated_at: timestamp,
|
|
103
|
+
};
|
|
104
|
+
let descriptor;
|
|
105
|
+
try {
|
|
106
|
+
descriptor = openSync(path, constants.O_CREAT | constants.O_EXCL | constants.O_WRONLY, 0o600);
|
|
107
|
+
}
|
|
108
|
+
catch (error) {
|
|
109
|
+
if (typeof error === "object" &&
|
|
110
|
+
error !== null &&
|
|
111
|
+
"code" in error &&
|
|
112
|
+
error.code === "EEXIST") {
|
|
113
|
+
const inspection = inspectPackageUpdateLock(options);
|
|
114
|
+
return inspection.status === "unlocked" ? { status: "active" } : inspection;
|
|
115
|
+
}
|
|
116
|
+
throw error;
|
|
117
|
+
}
|
|
118
|
+
try {
|
|
119
|
+
writeFileSync(descriptor, `${JSON.stringify(record)}\n`, "utf8");
|
|
120
|
+
}
|
|
121
|
+
finally {
|
|
122
|
+
closeSync(descriptor);
|
|
123
|
+
}
|
|
124
|
+
chmodSync(path, 0o600);
|
|
125
|
+
let released = false;
|
|
126
|
+
return {
|
|
127
|
+
status: "acquired",
|
|
128
|
+
lease: {
|
|
129
|
+
path,
|
|
130
|
+
transferTo: (pid) => {
|
|
131
|
+
if (released || !isPositiveInteger(pid)) {
|
|
132
|
+
throw new Error("Package update lock transfer is invalid.");
|
|
133
|
+
}
|
|
134
|
+
const current = parseRecord(path);
|
|
135
|
+
if (current?.owner_id !== ownerId) {
|
|
136
|
+
throw new Error("Package update lock ownership changed before transfer.");
|
|
137
|
+
}
|
|
138
|
+
writeRecord(path, {
|
|
139
|
+
...current,
|
|
140
|
+
pid,
|
|
141
|
+
updated_at: new Date(now()).toISOString(),
|
|
142
|
+
});
|
|
143
|
+
},
|
|
144
|
+
release: () => {
|
|
145
|
+
if (released) {
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
released = true;
|
|
149
|
+
if (parseRecord(path)?.owner_id !== ownerId) {
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
try {
|
|
153
|
+
unlinkSync(path);
|
|
154
|
+
}
|
|
155
|
+
catch (error) {
|
|
156
|
+
if (typeof error !== "object" ||
|
|
157
|
+
error === null ||
|
|
158
|
+
!("code" in error) ||
|
|
159
|
+
error.code !== "ENOENT") {
|
|
160
|
+
throw error;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
},
|
|
164
|
+
},
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
export function removeRecoverablePackageUpdateLock(options) {
|
|
168
|
+
if (inspectPackageUpdateLock(options).status !== "recovery_required") {
|
|
169
|
+
return false;
|
|
170
|
+
}
|
|
171
|
+
try {
|
|
172
|
+
unlinkSync(packageUpdateLockPath(options.tuttiHome));
|
|
173
|
+
return true;
|
|
174
|
+
}
|
|
175
|
+
catch {
|
|
176
|
+
return false;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
export function transferPackageUpdateLockFromCurrentProcess(options) {
|
|
180
|
+
if (!isPositiveInteger(options.nextPid)) {
|
|
181
|
+
return false;
|
|
182
|
+
}
|
|
183
|
+
const path = packageUpdateLockPath(options.tuttiHome);
|
|
184
|
+
const record = parseRecord(path);
|
|
185
|
+
if (record === null || record.pid !== process.pid) {
|
|
186
|
+
return false;
|
|
187
|
+
}
|
|
188
|
+
writeRecord(path, {
|
|
189
|
+
...record,
|
|
190
|
+
pid: options.nextPid,
|
|
191
|
+
updated_at: new Date((options.now ?? Date.now)()).toISOString(),
|
|
192
|
+
});
|
|
193
|
+
return true;
|
|
194
|
+
}
|
|
195
|
+
export function releasePackageUpdateLockForCurrentProcess(tuttiHome) {
|
|
196
|
+
const path = packageUpdateLockPath(tuttiHome);
|
|
197
|
+
const record = parseRecord(path);
|
|
198
|
+
if (record === null || record.pid !== process.pid) {
|
|
199
|
+
return false;
|
|
200
|
+
}
|
|
201
|
+
try {
|
|
202
|
+
unlinkSync(path);
|
|
203
|
+
return true;
|
|
204
|
+
}
|
|
205
|
+
catch {
|
|
206
|
+
return false;
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
export function releasePackageUpdateLockForExpectedPid(tuttiHome, expectedPid) {
|
|
210
|
+
const path = packageUpdateLockPath(tuttiHome);
|
|
211
|
+
const record = parseRecord(path);
|
|
212
|
+
if (record === null || record.pid !== expectedPid) {
|
|
213
|
+
return false;
|
|
214
|
+
}
|
|
215
|
+
try {
|
|
216
|
+
unlinkSync(path);
|
|
217
|
+
return true;
|
|
218
|
+
}
|
|
219
|
+
catch {
|
|
220
|
+
return false;
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
//# sourceMappingURL=package-update-lock.js.map
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export declare function packageUpdateLogPath(tuttiHome: string): string;
|
|
2
|
+
export declare function withPackageUpdateLogDescriptor<T>(tuttiHome: string, run: (descriptor: number) => T): T;
|
|
3
|
+
export declare function redactPackageUpdateOutput(value: string, privatePaths: readonly string[]): string;
|
|
4
|
+
export declare class PackageUpdateLogger {
|
|
5
|
+
#private;
|
|
6
|
+
constructor(options: {
|
|
7
|
+
tuttiHome: string;
|
|
8
|
+
privatePaths?: readonly string[];
|
|
9
|
+
now?: () => Date;
|
|
10
|
+
});
|
|
11
|
+
append(options: {
|
|
12
|
+
level: "info" | "warn" | "error";
|
|
13
|
+
message: string;
|
|
14
|
+
phase?: string;
|
|
15
|
+
reasonCode?: string;
|
|
16
|
+
stdout?: string;
|
|
17
|
+
stderr?: string;
|
|
18
|
+
}): void;
|
|
19
|
+
}
|
|
20
|
+
//# sourceMappingURL=package-update-log.d.ts.map
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { appendFileSync, chmodSync, closeSync, existsSync, mkdirSync, openSync, renameSync, statSync, unlinkSync, } from "node:fs";
|
|
2
|
+
import { tmpdir } from "node:os";
|
|
3
|
+
import { dirname, join } from "node:path";
|
|
4
|
+
import { redactAndTruncateText, serializeRuntimeLogLine } from "@tutti/shared/utils";
|
|
5
|
+
const PACKAGE_UPDATE_LOG_MAX_BYTES = 2 * 1024 * 1024;
|
|
6
|
+
const PACKAGE_UPDATE_OUTPUT_MAX_CHARS = 16 * 1024;
|
|
7
|
+
export function packageUpdateLogPath(tuttiHome) {
|
|
8
|
+
return join(tuttiHome, "logs", "package-update.log");
|
|
9
|
+
}
|
|
10
|
+
function preparePackageUpdateLog(tuttiHome) {
|
|
11
|
+
const path = packageUpdateLogPath(tuttiHome);
|
|
12
|
+
const directory = dirname(path);
|
|
13
|
+
mkdirSync(directory, { recursive: true, mode: 0o700 });
|
|
14
|
+
chmodSync(directory, 0o700);
|
|
15
|
+
if (existsSync(path) && statSync(path).size >= PACKAGE_UPDATE_LOG_MAX_BYTES) {
|
|
16
|
+
const previous = `${path}.1`;
|
|
17
|
+
if (existsSync(previous)) {
|
|
18
|
+
unlinkSync(previous);
|
|
19
|
+
}
|
|
20
|
+
renameSync(path, previous);
|
|
21
|
+
}
|
|
22
|
+
return path;
|
|
23
|
+
}
|
|
24
|
+
export function withPackageUpdateLogDescriptor(tuttiHome, run) {
|
|
25
|
+
const path = preparePackageUpdateLog(tuttiHome);
|
|
26
|
+
const descriptor = openSync(path, "a", 0o600);
|
|
27
|
+
chmodSync(path, 0o600);
|
|
28
|
+
try {
|
|
29
|
+
return run(descriptor);
|
|
30
|
+
}
|
|
31
|
+
finally {
|
|
32
|
+
closeSync(descriptor);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
export function redactPackageUpdateOutput(value, privatePaths) {
|
|
36
|
+
let redacted = redactAndTruncateText(value, PACKAGE_UPDATE_OUTPUT_MAX_CHARS);
|
|
37
|
+
for (const path of [...privatePaths, tmpdir()].filter((entry) => entry !== "")) {
|
|
38
|
+
redacted = redacted.replaceAll(path, "[REDACTED:local_path]");
|
|
39
|
+
}
|
|
40
|
+
return redacted.replace(/(^|[\s"'`=:(])(?:\/tmp\/|\/private\/tmp\/)[^\s"',)]+/gu, "$1[REDACTED:local_path]");
|
|
41
|
+
}
|
|
42
|
+
export class PackageUpdateLogger {
|
|
43
|
+
#tuttiHome;
|
|
44
|
+
#privatePaths;
|
|
45
|
+
#now;
|
|
46
|
+
constructor(options) {
|
|
47
|
+
this.#tuttiHome = options.tuttiHome;
|
|
48
|
+
this.#privatePaths = options.privatePaths ?? [];
|
|
49
|
+
this.#now = options.now ?? (() => new Date());
|
|
50
|
+
}
|
|
51
|
+
append(options) {
|
|
52
|
+
const path = preparePackageUpdateLog(this.#tuttiHome);
|
|
53
|
+
const fields = {
|
|
54
|
+
...(options.phase === undefined ? {} : { phase: options.phase }),
|
|
55
|
+
...(options.reasonCode === undefined ? {} : { reason_code: options.reasonCode }),
|
|
56
|
+
...(options.stdout === undefined
|
|
57
|
+
? {}
|
|
58
|
+
: { stdout: redactPackageUpdateOutput(options.stdout, this.#privatePaths) }),
|
|
59
|
+
...(options.stderr === undefined
|
|
60
|
+
? {}
|
|
61
|
+
: { stderr: redactPackageUpdateOutput(options.stderr, this.#privatePaths) }),
|
|
62
|
+
};
|
|
63
|
+
const line = serializeRuntimeLogLine({
|
|
64
|
+
level: options.level,
|
|
65
|
+
service: "tutti-package-update",
|
|
66
|
+
component: "worker",
|
|
67
|
+
message: options.message,
|
|
68
|
+
fields,
|
|
69
|
+
now: this.#now,
|
|
70
|
+
});
|
|
71
|
+
appendFileSync(path, `${line}\n`, { encoding: "utf8", mode: 0o600 });
|
|
72
|
+
chmodSync(path, 0o600);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
//# sourceMappingURL=package-update-log.js.map
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { PackageUpdatePreparationLease } from "./package-update-service.js";
|
|
2
|
+
import { type PackageUpdateWorkerInput } from "./package-update-worker.js";
|
|
3
|
+
export type HiddenPackageUpdateProcessPlan = {
|
|
4
|
+
executable: string;
|
|
5
|
+
args: string[];
|
|
6
|
+
cwd: string;
|
|
7
|
+
env: NodeJS.ProcessEnv;
|
|
8
|
+
shell: false;
|
|
9
|
+
};
|
|
10
|
+
export declare function buildHiddenPackageUpdateProcessPlan(options: {
|
|
11
|
+
input: PackageUpdateWorkerInput;
|
|
12
|
+
role: "worker" | "completion";
|
|
13
|
+
}): HiddenPackageUpdateProcessPlan;
|
|
14
|
+
export declare function spawnPackageUpdateWorkerProcess(options: {
|
|
15
|
+
input: PackageUpdateWorkerInput;
|
|
16
|
+
lease: PackageUpdatePreparationLease;
|
|
17
|
+
}): Promise<{
|
|
18
|
+
pid: number;
|
|
19
|
+
}>;
|
|
20
|
+
export declare function runPackageUpdateWorkerProtocol(): Promise<void>;
|
|
21
|
+
export declare function runPackageUpdateCompletionProtocol(): Promise<void>;
|
|
22
|
+
//# sourceMappingURL=package-update-process.d.ts.map
|