History

fix(service): make install reconcile current service

t3-code[bot] committed 2 months ago

Commit f98cfca1 parent f708f633 files · +62 −38Browse files

Unified · Split

3 files
  1. apps/server/src/cli/service.test.ts +36 −1
  2. apps/server/src/cli/service.ts +22 −30
  3. docs/user/background-service.md +4 −7
apps/server/src/cli/service.test.ts+36−1
@@ -1,6 +1,8 @@
1import { assert, it } from "@effect/vitest";1import { assert, it } from "@effect/vitest";
2import * as Effect from "effect/Effect";
2 3
3import { formatServiceStatus } from "./service.ts";4import * as BootService from "../cloud/bootService.ts";
5import { formatServiceStatus, reconcileService } from "./service.ts";
4 6
5const status = {7const status = {
6 supported: true,8 supported: true,
@@ -35,3 +37,36 @@ it("explains where the service is supported", () => { · 28 unmodified lines
35 "Supported on: Linux with systemd, macOS with launchd",37 "Supported on: Linux with systemd, macOS with launchd",
36 );38 );
37});39});
40
41it.effect("restarts an already-current service during reconciliation", () =>
42 Effect.gen(function* () {
43 let installCount = 0;
44 const plan = {
45 nodePath: "/usr/bin/node",
46 launcherPath: "/home/me/.t3/runtime/service-launcher.mjs",
47 baseDir: "/home/me/.t3",
48 logPath: status.logPath,
49 unitPath: status.unitPath,
50 };
51 const service = BootService.BootService.of({
52 status: Effect.succeed(status),
53 install: Effect.sync(() => {
54 installCount += 1;
55 return plan;
56 }),
57 uninstall: Effect.succeed(false),
58 });
59
60 const result = yield* reconcileService().pipe(
61 Effect.provideService(BootService.BootService, service),
62 );
63
64 assert.equal(installCount, 1);
65 assert.deepEqual(result, {
66 changed: true,
67 previouslyInstalled: true,
68 previouslyCurrent: true,
69 plan,
70 });
71 }),
72);
apps/server/src/cli/service.ts+22−30
@@ -18,28 +18,22 @@ export const bootServiceLayer = (config: ServerConfig.ServerConfig["Service"]) = · 17 unmodified lines
18 cliVersion: packageJson.version,18 cliVersion: packageJson.version,
19 }).pipe(Layer.provide(ProcessRunner.layer));19 }).pipe(Layer.provide(ProcessRunner.layer));
20 20
21export type ServiceReconcileResult =21export interface ServiceReconcileResult {
22 | {22 readonly changed: true;
23 readonly changed: false;23 readonly previouslyInstalled: boolean;
24 readonly status: BootService.BootServiceStatus;24 readonly previouslyCurrent: boolean;
25 }25 readonly plan: BootService.BootServicePlan;
26 | {26}
27 readonly changed: true;
28 readonly previouslyInstalled: boolean;
29 readonly plan: BootService.BootServicePlan;
30 };
31 27
32/** Install, update, or repair the service using the CLI version running this command. */28/** Install, update, or repair the service using the CLI version running this command. */
33export const reconcileService = Effect.fn("cli.service.reconcile")(function* () {29export const reconcileService = Effect.fn("cli.service.reconcile")(function* () {
34 const service = yield* BootService.BootService;30 const service = yield* BootService.BootService;
35 const status = yield* service.status;31 const status = yield* service.status;
36 if (status.installed && status.current) {
37 return { changed: false, status } satisfies ServiceReconcileResult;
38 }
39 const plan = yield* service.install;32 const plan = yield* service.install;
40 return {33 return {
41 changed: true,34 changed: true,
42 previouslyInstalled: status.installed,35 previouslyInstalled: status.installed,
36 previouslyCurrent: status.current,
43 plan,37 plan,
44 } satisfies ServiceReconcileResult;38 } satisfies ServiceReconcileResult;
45});39});
@@ -79,14 +73,13 @@ const serviceInstallCommand = Command.make("install", projectLocationFlags).pipe · 33 unmodified lines
79 flags,73 flags,
80 Effect.gen(function* () {74 Effect.gen(function* () {
81 const result = yield* reconcileService();75 const result = yield* reconcileService();
82 if (!result.changed) {76 const action = result.previouslyCurrent
83 yield* Console.log(77 ? "Restarted"
84 `T3 Code service is already installed with t3@${packageJson.version}.`,78 : result.previouslyInstalled
85 );79 ? "Updated and restarted"
86 return;80 : "Installed and started";
87 }
88 yield* Console.log(81 yield* Console.log(
89 `${result.previouslyInstalled ? "Updated" : "Installed"} T3 Code service with t3@${packageJson.version}.\nLogs: ${result.plan.logPath}`,82 `${action} T3 Code service with t3@${packageJson.version}.\nLogs: ${result.plan.logPath}`,
90 );83 );
91 }),84 }),
92 ),85 ),
@@ -102,12 +95,13 @@ const serviceUpdateCommand = Command.make("update", projectLocationFlags).pipe( · 9 unmodified lines
102 flags,95 flags,
103 Effect.gen(function* () {96 Effect.gen(function* () {
104 const result = yield* reconcileService();97 const result = yield* reconcileService();
105 if (!result.changed) {98 const action = result.previouslyCurrent
106 yield* Console.log(`T3 Code service is already using t3@${packageJson.version}.`);99 ? "Restarted"
107 return;100 : result.previouslyInstalled
108 }101 ? "Updated and restarted"
102 : "Installed and started";
109 yield* Console.log(103 yield* Console.log(
110 `${result.previouslyInstalled ? "Updated" : "Installed"} T3 Code service with t3@${packageJson.version}.\nLogs: ${result.plan.logPath}`,104 `${action} T3 Code service with t3@${packageJson.version}.\nLogs: ${result.plan.logPath}`,
111 );105 );
112 }),106 }),
113 ),107 ),
@@ -172,11 +166,9 @@ export const offerServiceDuringOnboarding = Effect.gen(function* () { · 58 unmodified lines
172 return false;166 return false;
173 }167 }
174 const result = yield* reconcileService();168 const result = yield* reconcileService();
175 if (result.changed) {169 yield* Console.log(
176 yield* Console.log(170 `Background service ${result.previouslyInstalled ? "updated" : "installed"}. Logs: ${result.plan.logPath}`,
177 `Background service ${result.previouslyInstalled ? "updated" : "installed"}. Logs: ${result.plan.logPath}`,171 );
178 );
179 }
180 return true;172 return true;
181});173});
182 174
docs/user/background-service.md+4−7
@@ -11,25 +11,22 @@ Install it with the latest T3 Code release: · 10 unmodified lines
11npx t3@latest service install11npx t3@latest service install
12```12```
13 13
14The same command repairs and restarts an existing service, so there is no separate restart step.
15
14Check whether it is installed:16Check whether it is installed:
15 17
16```sh18```sh
17npx t3@latest service status19npx t3@latest service status
18```20```
19 21
20Update or repair it:
21
22```sh
23npx t3@latest service update
24```
25
26Stop it and remove it from startup:22Stop it and remove it from startup:
27 23
28```sh24```sh
29npx t3@latest service uninstall25npx t3@latest service uninstall
30```26```
31 27
32Updating restarts T3 Code briefly. Let active agent work and terminal commands finish first.28Repairing with `service install` restarts T3 Code briefly. Let active agent work and terminal
29commands finish first.
33If a remote update is already in progress, wait for it to finish before retrying a local update.30If a remote update is already in progress, wait for it to finish before retrying a local update.
34 31
35The service runs a small stable launcher. Exact T3 Code versions are installed separately, so a32The service runs a small stable launcher. Exact T3 Code versions are installed separately, so a