@p4code/cli 0.0.48 → 0.0.50
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/bin.mjs +470 -48
- package/dist/client/assets/{DiffPanel-b2Pr9Otk.js → DiffPanel-BUvVWIMX.js} +2 -2
- package/dist/client/assets/{FilePreviewPanel-BiDCpfMC.js → FilePreviewPanel-Bk7x7hMd.js} +2 -2
- package/dist/client/assets/{PreviewPanel-dhrCEx4z.js → PreviewPanel-CR9iyxF2.js} +2 -2
- package/dist/client/assets/{fileCommentAnnotations-MTIvLMIx.js → fileCommentAnnotations-OW7TQ0uO.js} +2 -2
- package/dist/client/assets/{index-CeacS9V2.js → index-CHboeMs4.js} +4 -4
- package/dist/client/index.html +1 -1
- package/package.json +1 -1
package/dist/bin.mjs
CHANGED
|
@@ -55,6 +55,7 @@ import * as Exit from "effect/Exit";
|
|
|
55
55
|
import * as SchemaGetter from "effect/SchemaGetter";
|
|
56
56
|
import * as Config from "effect/Config";
|
|
57
57
|
import * as NodeReadline from "node:readline";
|
|
58
|
+
import * as Clock from "effect/Clock";
|
|
58
59
|
import * as Cache from "effect/Cache";
|
|
59
60
|
import * as Deferred from "effect/Deferred";
|
|
60
61
|
import * as Equal from "effect/Equal";
|
|
@@ -62,7 +63,6 @@ import * as Semaphore from "effect/Semaphore";
|
|
|
62
63
|
import * as Scope from "effect/Scope";
|
|
63
64
|
import { parse } from "yaml";
|
|
64
65
|
import * as HttpApiClient from "effect/unstable/httpapi/HttpApiClient";
|
|
65
|
-
import * as Clock from "effect/Clock";
|
|
66
66
|
import * as Metric from "effect/Metric";
|
|
67
67
|
import * as Queue from "effect/Queue";
|
|
68
68
|
import { cast, dual } from "effect/Function";
|
|
@@ -121,7 +121,7 @@ const closeServer = (server) => {
|
|
|
121
121
|
* NetService - Service tag for startup networking helpers.
|
|
122
122
|
*/
|
|
123
123
|
var NetService = class extends Context.Service()("@p4code/shared/Net/NetService") {};
|
|
124
|
-
const make$
|
|
124
|
+
const make$72 = () => {
|
|
125
125
|
/**
|
|
126
126
|
* Returns true when a TCP server can bind to {host, port}.
|
|
127
127
|
* `EADDRNOTAVAIL` is treated as available so IPv6-absent hosts don't fail
|
|
@@ -230,10 +230,10 @@ const make$71 = () => {
|
|
|
230
230
|
})
|
|
231
231
|
};
|
|
232
232
|
};
|
|
233
|
-
const layer$
|
|
233
|
+
const layer$68 = Layer.sync(NetService, make$72);
|
|
234
234
|
//#endregion
|
|
235
235
|
//#region package.json
|
|
236
|
-
var version = "0.0.
|
|
236
|
+
var version = "0.0.50";
|
|
237
237
|
//#endregion
|
|
238
238
|
//#region src/config.ts
|
|
239
239
|
/**
|
|
@@ -254,8 +254,8 @@ var ServerConfig$1 = class extends Context.Service()("@p4code/cli/config/ServerC
|
|
|
254
254
|
/** @deprecated Import and use `layerTest` from this module. */
|
|
255
255
|
static layerTest = (cwd, baseDirOrPrefix) => layerTest$2(cwd, baseDirOrPrefix);
|
|
256
256
|
};
|
|
257
|
-
const make$
|
|
258
|
-
const layer$
|
|
257
|
+
const make$71 = (config) => ServerConfig$1.of(config);
|
|
258
|
+
const layer$67 = (config) => Layer.succeed(ServerConfig$1, make$71(config));
|
|
259
259
|
const deriveServerPaths = Effect.fn(function* (baseDir, devUrl, options = {}) {
|
|
260
260
|
const { join } = yield* Path.Path;
|
|
261
261
|
const stateDir = join(baseDir, devUrl !== void 0 && !options.baseDirIsExplicit ? "dev" : "userdata");
|
|
@@ -869,7 +869,7 @@ function compareSemverVersions(left, right) {
|
|
|
869
869
|
* timeout, every failure swallowed, and never anything that changes an exit
|
|
870
870
|
* code. A version check that can break a command is worse than no check.
|
|
871
871
|
*/
|
|
872
|
-
const PACKAGE_NAME = "@p4code/cli";
|
|
872
|
+
const PACKAGE_NAME$1 = "@p4code/cli";
|
|
873
873
|
const CHECK_INTERVAL = Duration.hours(24);
|
|
874
874
|
const REQUEST_TIMEOUT = Duration.seconds(3);
|
|
875
875
|
/**
|
|
@@ -889,7 +889,7 @@ const encodeCacheEntry = Schema$1.encodeEffect(CacheEntryJson);
|
|
|
889
889
|
const NpmLatest = Schema$1.Struct({ version: Schema$1.optional(Schema$1.String) });
|
|
890
890
|
const fetchLatestVersion = Effect.fn("cli.updateCheck.fetch")(function* () {
|
|
891
891
|
const client = yield* HttpClient.HttpClient;
|
|
892
|
-
const request = HttpClientRequest.get(`https://registry.npmjs.org/${encodeURIComponent(PACKAGE_NAME)}/latest`).pipe(HttpClientRequest.setHeader("accept", "application/json"));
|
|
892
|
+
const request = HttpClientRequest.get(`https://registry.npmjs.org/${encodeURIComponent(PACKAGE_NAME$1)}/latest`).pipe(HttpClientRequest.setHeader("accept", "application/json"));
|
|
893
893
|
const response = yield* client.execute(request).pipe(Effect.timeout(REQUEST_TIMEOUT));
|
|
894
894
|
if (response.status < 200 || response.status >= 300) return null;
|
|
895
895
|
const version = (yield* response.json.pipe(Effect.flatMap(Schema$1.decodeUnknownEffect(NpmLatest)))).version?.trim();
|
|
@@ -923,7 +923,7 @@ const resolveLatestVersion = Effect.fn("cli.updateCheck.resolve")(function* (sta
|
|
|
923
923
|
* `service update` restarts it onto the new one.
|
|
924
924
|
*/
|
|
925
925
|
function formatUpdateReminder(input) {
|
|
926
|
-
return [`Update available: ${PACKAGE_NAME}@${input.latestVersion} (running ${input.currentVersion}).`, ` npm i -g ${PACKAGE_NAME}@latest && p4c service update`].join("\n");
|
|
926
|
+
return [`Update available: ${PACKAGE_NAME$1}@${input.latestVersion} (running ${input.currentVersion}).`, ` npm i -g ${PACKAGE_NAME$1}@latest && p4c service update`].join("\n");
|
|
927
927
|
}
|
|
928
928
|
/**
|
|
929
929
|
* The reminder line, or null when there is nothing to say. Callers print it;
|
|
@@ -8736,7 +8736,7 @@ function deriveAuthClientMetadata(input) {
|
|
|
8736
8736
|
//#endregion
|
|
8737
8737
|
//#region src/auth/EnvironmentAuthPolicy.ts
|
|
8738
8738
|
var EnvironmentAuthPolicy = class extends Context.Service()("@p4code/cli/auth/EnvironmentAuthPolicy") {};
|
|
8739
|
-
const make$
|
|
8739
|
+
const make$70 = Effect.gen(function* () {
|
|
8740
8740
|
const config = yield* ServerConfig$1;
|
|
8741
8741
|
const isRemoteReachable = isRemoteReachableHost(config.host);
|
|
8742
8742
|
const policy = config.mode === "desktop" ? isRemoteReachable ? "remote-reachable" : "desktop-managed-local" : isRemoteReachable ? "remote-reachable" : "loopback-browser";
|
|
@@ -8754,7 +8754,7 @@ const make$69 = Effect.gen(function* () {
|
|
|
8754
8754
|
};
|
|
8755
8755
|
return EnvironmentAuthPolicy.of({ getDescriptor: () => Effect.succeed(descriptor).pipe(Effect.withSpan("EnvironmentAuthPolicy.getDescriptor")) });
|
|
8756
8756
|
});
|
|
8757
|
-
const layer$
|
|
8757
|
+
const layer$66 = Layer.effect(EnvironmentAuthPolicy, make$70);
|
|
8758
8758
|
//#endregion
|
|
8759
8759
|
//#region src/persistence/Errors.ts
|
|
8760
8760
|
function summarizeSchemaIssue(issue) {
|
|
@@ -8935,7 +8935,7 @@ function toPersistenceSqlOrDecodeError$6(sqlOperation, decodeOperation, correlat
|
|
|
8935
8935
|
cause
|
|
8936
8936
|
});
|
|
8937
8937
|
}
|
|
8938
|
-
const make$
|
|
8938
|
+
const make$69 = Effect.gen(function* () {
|
|
8939
8939
|
const sql = yield* SqlClient.SqlClient;
|
|
8940
8940
|
const createSessionRow = SqlSchema.void({
|
|
8941
8941
|
Request: CreateAuthSessionInput,
|
|
@@ -9069,7 +9069,7 @@ const make$68 = Effect.gen(function* () {
|
|
|
9069
9069
|
setLastConnectedAt
|
|
9070
9070
|
};
|
|
9071
9071
|
});
|
|
9072
|
-
const layer$
|
|
9072
|
+
const layer$65 = Layer.effect(AuthSessionRepository, make$69);
|
|
9073
9073
|
//#endregion
|
|
9074
9074
|
//#region src/auth/ServerSecretStore.ts
|
|
9075
9075
|
const secretStoreErrorContext = {
|
|
@@ -9136,7 +9136,7 @@ const isSecretStoreError = Schema$1.is(SecretStoreError);
|
|
|
9136
9136
|
const isPlatformError = (value) => Predicate.isTagged(value, "PlatformError");
|
|
9137
9137
|
const isSecretAlreadyExistsError = (error) => "cause" in error && isPlatformError(error.cause) && error.cause.reason._tag === "AlreadyExists";
|
|
9138
9138
|
var ServerSecretStore = class extends Context.Service()("@p4code/cli/auth/ServerSecretStore") {};
|
|
9139
|
-
const make$
|
|
9139
|
+
const make$68 = Effect.gen(function* () {
|
|
9140
9140
|
const crypto = yield* Crypto.Crypto;
|
|
9141
9141
|
const fileSystem = yield* FileSystem.FileSystem;
|
|
9142
9142
|
const path = yield* Path.Path;
|
|
@@ -9206,7 +9206,7 @@ const make$67 = Effect.gen(function* () {
|
|
|
9206
9206
|
remove
|
|
9207
9207
|
});
|
|
9208
9208
|
});
|
|
9209
|
-
const layer$
|
|
9209
|
+
const layer$64 = Layer.effect(ServerSecretStore, make$68);
|
|
9210
9210
|
//#endregion
|
|
9211
9211
|
//#region src/auth/SessionStore.ts
|
|
9212
9212
|
var MalformedSessionTokenError = class extends Schema$1.TaggedErrorClass()("MalformedSessionTokenError", {}) {
|
|
@@ -9444,7 +9444,7 @@ function toAuthClientSession(input) {
|
|
|
9444
9444
|
current: false
|
|
9445
9445
|
};
|
|
9446
9446
|
}
|
|
9447
|
-
const make$
|
|
9447
|
+
const make$67 = Effect.gen(function* () {
|
|
9448
9448
|
const crypto = yield* Crypto.Crypto;
|
|
9449
9449
|
const serverConfig = yield* ServerConfig$1;
|
|
9450
9450
|
const secretStore = yield* ServerSecretStore;
|
|
@@ -9758,7 +9758,7 @@ const make$66 = Effect.gen(function* () {
|
|
|
9758
9758
|
markDisconnected
|
|
9759
9759
|
});
|
|
9760
9760
|
});
|
|
9761
|
-
const layer$
|
|
9761
|
+
const layer$63 = Layer.effect(SessionStore, make$67).pipe(Layer.provideMerge(layer$65));
|
|
9762
9762
|
//#endregion
|
|
9763
9763
|
//#region src/persistence/AuthPairingLinks.ts
|
|
9764
9764
|
const AuthPairingLinkRecord = Schema$1.Struct({
|
|
@@ -9819,7 +9819,7 @@ function toPersistenceSqlOrDecodeError$5(sqlOperation, decodeOperation, correlat
|
|
|
9819
9819
|
cause
|
|
9820
9820
|
});
|
|
9821
9821
|
}
|
|
9822
|
-
const make$
|
|
9822
|
+
const make$66 = Effect.gen(function* () {
|
|
9823
9823
|
const sql = yield* SqlClient.SqlClient;
|
|
9824
9824
|
const createPairingLinkRow = SqlSchema.void({
|
|
9825
9825
|
Request: CreateAuthPairingLinkInput,
|
|
@@ -9954,7 +9954,7 @@ const make$65 = Effect.gen(function* () {
|
|
|
9954
9954
|
getByCredential
|
|
9955
9955
|
};
|
|
9956
9956
|
});
|
|
9957
|
-
const layer$
|
|
9957
|
+
const layer$62 = Layer.effect(AuthPairingLinkRepository, make$66);
|
|
9958
9958
|
//#endregion
|
|
9959
9959
|
//#region src/auth/PairingGrantStore.ts
|
|
9960
9960
|
var UnknownBootstrapCredentialError = class extends Schema$1.TaggedErrorClass()("UnknownBootstrapCredentialError", {}) {
|
|
@@ -10049,7 +10049,7 @@ const DEV_STARTUP_TTL_HOURS = Duration.hours(24);
|
|
|
10049
10049
|
const PAIRING_TOKEN_ALPHABET = "23456789ABCDEFGHJKLMNPQRSTUVWXYZ";
|
|
10050
10050
|
const PAIRING_TOKEN_LENGTH = 12;
|
|
10051
10051
|
const PAIRING_TOKEN_REJECTION_LIMIT = Math.floor(256 / 32) * 32;
|
|
10052
|
-
const make$
|
|
10052
|
+
const make$65 = Effect.gen(function* () {
|
|
10053
10053
|
const crypto = yield* Crypto.Crypto;
|
|
10054
10054
|
const config = yield* ServerConfig$1;
|
|
10055
10055
|
const pairingLinks = yield* AuthPairingLinkRepository;
|
|
@@ -10247,7 +10247,7 @@ const make$64 = Effect.gen(function* () {
|
|
|
10247
10247
|
consume
|
|
10248
10248
|
});
|
|
10249
10249
|
});
|
|
10250
|
-
const layer$
|
|
10250
|
+
const layer$61 = Layer.effect(PairingGrantStore, make$65).pipe(Layer.provideMerge(layer$62));
|
|
10251
10251
|
//#endregion
|
|
10252
10252
|
//#region src/persistence/Migrations/001_OrchestrationEvents.ts
|
|
10253
10253
|
var _001_OrchestrationEvents_default = Effect.gen(function* () {
|
|
@@ -12020,7 +12020,7 @@ function parseBearerToken(request) {
|
|
|
12020
12020
|
const token = header.slice(7).trim();
|
|
12021
12021
|
return token.length > 0 ? token : null;
|
|
12022
12022
|
}
|
|
12023
|
-
const make$
|
|
12023
|
+
const make$64 = Effect.gen(function* () {
|
|
12024
12024
|
const policy = yield* EnvironmentAuthPolicy;
|
|
12025
12025
|
const bootstrapCredentials = yield* PairingGrantStore;
|
|
12026
12026
|
const sessions = yield* SessionStore;
|
|
@@ -12215,9 +12215,9 @@ const make$63 = Effect.gen(function* () {
|
|
|
12215
12215
|
issueStartupPairingUrl
|
|
12216
12216
|
});
|
|
12217
12217
|
});
|
|
12218
|
-
const layer$
|
|
12219
|
-
const storageLayer = Layer.mergeAll(layer$
|
|
12220
|
-
const runtimeLayer = layer$
|
|
12218
|
+
const layer$60 = Layer.effect(EnvironmentAuth, make$64).pipe(Layer.provideMerge(layer$61), Layer.provideMerge(layer$63), Layer.provideMerge(layer$66));
|
|
12219
|
+
const storageLayer = Layer.mergeAll(layer$64, layerConfig);
|
|
12220
|
+
const runtimeLayer = layer$60.pipe(Layer.provideMerge(storageLayer));
|
|
12221
12221
|
//#endregion
|
|
12222
12222
|
//#region src/cliAuthFormat.ts
|
|
12223
12223
|
const newline = "\n";
|
|
@@ -12763,8 +12763,8 @@ const noBrowserFlag = Flag.boolean("no-browser").pipe(Flag.withDescription("Disa
|
|
|
12763
12763
|
const bootstrapFdFlag = Flag.integer("bootstrap-fd").pipe(Flag.withSchema(Schema$1.Int), Flag.withDescription("Read one-time bootstrap secrets from the given file descriptor."), Flag.optional);
|
|
12764
12764
|
const autoBootstrapProjectFromCwdFlag = Flag.boolean("auto-bootstrap-project-from-cwd").pipe(Flag.withDescription("Create a project for the current working directory on startup when missing."), Flag.optional);
|
|
12765
12765
|
const logWebSocketEventsFlag = Flag.boolean("log-websocket-events").pipe(Flag.withDescription("Emit server-side logs for outbound WebSocket push traffic (equivalent to P4CODE_LOG_WS_EVENTS)."), Flag.withAlias("log-ws-events"), Flag.optional);
|
|
12766
|
-
const tailscaleServeFlag = Flag.boolean("tailscale-serve").pipe(Flag.withDescription("Configure Tailscale Serve to expose this backend over HTTPS on the Tailnet."), Flag.optional);
|
|
12767
|
-
const tailscaleServePortFlag = Flag.integer("tailscale-serve-port").pipe(Flag.withSchema(PortSchema), Flag.withDescription("HTTPS port for Tailscale Serve when --tailscale-serve is enabled."), Flag.optional);
|
|
12766
|
+
const tailscaleServeFlag$1 = Flag.boolean("tailscale-serve").pipe(Flag.withDescription("Configure Tailscale Serve to expose this backend over HTTPS on the Tailnet."), Flag.optional);
|
|
12767
|
+
const tailscaleServePortFlag$1 = Flag.integer("tailscale-serve-port").pipe(Flag.withSchema(PortSchema), Flag.withDescription("HTTPS port for Tailscale Serve when --tailscale-serve is enabled."), Flag.optional);
|
|
12768
12768
|
const EnvServerConfig = Config.all({
|
|
12769
12769
|
logLevel: Config.logLevel("P4CODE_LOG_LEVEL").pipe(Config.withDefault("Info")),
|
|
12770
12770
|
traceMinLevel: Config.logLevel("P4CODE_TRACE_MIN_LEVEL").pipe(Config.withDefault("Info")),
|
|
@@ -12806,8 +12806,8 @@ const sharedServerCommandFlags = {
|
|
|
12806
12806
|
bootstrapFd: bootstrapFdFlag,
|
|
12807
12807
|
autoBootstrapProjectFromCwd: autoBootstrapProjectFromCwdFlag,
|
|
12808
12808
|
logWebSocketEvents: logWebSocketEventsFlag,
|
|
12809
|
-
tailscaleServeEnabled: tailscaleServeFlag,
|
|
12810
|
-
tailscaleServePort: tailscaleServePortFlag
|
|
12809
|
+
tailscaleServeEnabled: tailscaleServeFlag$1,
|
|
12810
|
+
tailscaleServePort: tailscaleServePortFlag$1
|
|
12811
12811
|
};
|
|
12812
12812
|
const authLocationFlags = sharedServerLocationFlags;
|
|
12813
12813
|
const resolveOptionPrecedence = (...values) => Option.firstSomeOf(values);
|
|
@@ -12972,7 +12972,7 @@ const runWithEnvironmentAuth = (flags, run, options) => Effect.gen(function* ()
|
|
|
12972
12972
|
}
|
|
12973
12973
|
return yield* Effect.gen(function* () {
|
|
12974
12974
|
return yield* run(yield* EnvironmentAuth);
|
|
12975
|
-
}).pipe(Effect.provide(Layer.mergeAll(runtimeLayer).pipe(Layer.provide(layer$
|
|
12975
|
+
}).pipe(Effect.provide(Layer.mergeAll(runtimeLayer).pipe(Layer.provide(layer$67(config)), Layer.provide(Layer.succeed(References.MinimumLogLevel, minimumLogLevel)))));
|
|
12976
12976
|
});
|
|
12977
12977
|
const ttlFlag = Flag.string("ttl").pipe(Flag.withSchema(DurationFromString), Flag.withDescription("TTL, for example `5m`, `1h`, `30d`, or `15 minutes`."), Flag.optional);
|
|
12978
12978
|
const jsonFlag = Flag.boolean("json").pipe(Flag.withDescription("Emit JSON instead of human-readable output."), Flag.withDefault(false));
|
|
@@ -13069,6 +13069,64 @@ const authCommand = Command.make("auth").pipe(Command.withDescription("Manage th
|
|
|
13069
13069
|
* @module HubConfig
|
|
13070
13070
|
*/
|
|
13071
13071
|
var HubConfig = class extends Context.Service()("@p4code/cli/hub/HubConfig") {};
|
|
13072
|
+
const FAILURE_WINDOW_MS = 6e4;
|
|
13073
|
+
const BLOCK_DURATION_MS = 6e4;
|
|
13074
|
+
const initialThrottleState = {
|
|
13075
|
+
windowStartMillis: 0,
|
|
13076
|
+
failures: 0,
|
|
13077
|
+
blockedUntilMillis: 0
|
|
13078
|
+
};
|
|
13079
|
+
/** Whether a *failing* request should be refused outright rather than checked. */
|
|
13080
|
+
const isBlocked = (state, nowMillis) => nowMillis < state.blockedUntilMillis;
|
|
13081
|
+
/** Seconds a caller should wait, for `retry-after`. Always at least one. */
|
|
13082
|
+
const retryAfterSeconds = (state, nowMillis) => Math.max(1, Math.ceil((state.blockedUntilMillis - nowMillis) / 1e3));
|
|
13083
|
+
/**
|
|
13084
|
+
* Count a failure, and start blocking once the window fills.
|
|
13085
|
+
*
|
|
13086
|
+
* The window is fixed rather than sliding: a sliding window needs the timestamp
|
|
13087
|
+
* of every attempt, which is unbounded memory for an unbounded attacker. A
|
|
13088
|
+
* fixed window lets a burst straddle a boundary and land up to twice the limit,
|
|
13089
|
+
* which for this purpose is a rounding error against the size of the search
|
|
13090
|
+
* space.
|
|
13091
|
+
*/
|
|
13092
|
+
const recordFailure = (state, nowMillis, limits = {
|
|
13093
|
+
maxFailures: 10,
|
|
13094
|
+
windowMillis: FAILURE_WINDOW_MS,
|
|
13095
|
+
blockMillis: BLOCK_DURATION_MS
|
|
13096
|
+
}) => {
|
|
13097
|
+
if (nowMillis - state.windowStartMillis >= limits.windowMillis) return {
|
|
13098
|
+
windowStartMillis: nowMillis,
|
|
13099
|
+
failures: 1,
|
|
13100
|
+
blockedUntilMillis: 0
|
|
13101
|
+
};
|
|
13102
|
+
const failures = state.failures + 1;
|
|
13103
|
+
return {
|
|
13104
|
+
windowStartMillis: state.windowStartMillis,
|
|
13105
|
+
failures,
|
|
13106
|
+
blockedUntilMillis: failures >= limits.maxFailures ? nowMillis + limits.blockMillis : state.blockedUntilMillis
|
|
13107
|
+
};
|
|
13108
|
+
};
|
|
13109
|
+
var HubAuthThrottle = class extends Context.Service()("@p4code/cli/hub/HubAuthThrottle") {};
|
|
13110
|
+
const DEFAULT_LIMITS = {
|
|
13111
|
+
maxFailures: 10,
|
|
13112
|
+
windowMillis: FAILURE_WINDOW_MS,
|
|
13113
|
+
blockMillis: BLOCK_DURATION_MS
|
|
13114
|
+
};
|
|
13115
|
+
const make$63 = Effect.fn("HubAuthThrottle.make")(function* (limits = DEFAULT_LIMITS) {
|
|
13116
|
+
const state = yield* Ref.make(initialThrottleState);
|
|
13117
|
+
return HubAuthThrottle.of({
|
|
13118
|
+
shouldRefuse: Effect.gen(function* () {
|
|
13119
|
+
const now = yield* Clock.currentTimeMillis;
|
|
13120
|
+
return isBlocked(yield* Ref.get(state), now);
|
|
13121
|
+
}),
|
|
13122
|
+
recordFailure: Effect.gen(function* () {
|
|
13123
|
+
const now = yield* Clock.currentTimeMillis;
|
|
13124
|
+
const next = yield* Ref.updateAndGet(state, (current) => recordFailure(current, now, limits));
|
|
13125
|
+
return retryAfterSeconds(next, now);
|
|
13126
|
+
})
|
|
13127
|
+
});
|
|
13128
|
+
});
|
|
13129
|
+
const layer$59 = Layer.effect(HubAuthThrottle, make$63());
|
|
13072
13130
|
//#endregion
|
|
13073
13131
|
//#region src/hub/HubAuth.ts
|
|
13074
13132
|
/**
|
|
@@ -13127,6 +13185,31 @@ var HubUnauthorizedError = class {
|
|
|
13127
13185
|
_tag = "HubUnauthorizedError";
|
|
13128
13186
|
};
|
|
13129
13187
|
/**
|
|
13188
|
+
* Too many failed attempts recently; this one was not even checked.
|
|
13189
|
+
*
|
|
13190
|
+
* Distinct from `HubUnauthorizedError` so the response can differ. A 429 is the
|
|
13191
|
+
* honest answer — the credential was not evaluated, so calling it invalid would
|
|
13192
|
+
* be a claim the hub did not make — and it tells a legitimate server retrying a
|
|
13193
|
+
* stale token to back off rather than hammer.
|
|
13194
|
+
*/
|
|
13195
|
+
var HubThrottledError = class {
|
|
13196
|
+
_tag = "HubThrottledError";
|
|
13197
|
+
retryAfterSeconds;
|
|
13198
|
+
constructor(retryAfterSeconds) {
|
|
13199
|
+
this.retryAfterSeconds = retryAfterSeconds;
|
|
13200
|
+
}
|
|
13201
|
+
};
|
|
13202
|
+
const hubThrottledResponse = (retryAfterSeconds) => HttpServerResponse.jsonUnsafe({
|
|
13203
|
+
error: "too_many_hub_credential_failures",
|
|
13204
|
+
message: "Too many failed credential attempts. Try again shortly."
|
|
13205
|
+
}, {
|
|
13206
|
+
status: 429,
|
|
13207
|
+
headers: {
|
|
13208
|
+
"cache-control": "no-store",
|
|
13209
|
+
"retry-after": String(retryAfterSeconds)
|
|
13210
|
+
}
|
|
13211
|
+
});
|
|
13212
|
+
/**
|
|
13130
13213
|
* Fails with {@link HubUnauthorizedError} unless the request carries a
|
|
13131
13214
|
* configured bearer token. Route handlers run this first and let
|
|
13132
13215
|
* `respondToHubAuthFailure` turn the failure into a 401.
|
|
@@ -13134,11 +13217,17 @@ var HubUnauthorizedError = class {
|
|
|
13134
13217
|
const authenticateHubRequest = Effect.fn("HubAuth.authenticateHubRequest")(function* () {
|
|
13135
13218
|
const request = yield* HttpServerRequest.HttpServerRequest;
|
|
13136
13219
|
const { tokenHashes } = yield* HubConfig;
|
|
13220
|
+
const throttle = yield* HubAuthThrottle;
|
|
13137
13221
|
const token = readBearerToken(request.headers.authorization);
|
|
13138
|
-
if (
|
|
13139
|
-
|
|
13140
|
-
|
|
13222
|
+
if (isAuthorizedHubToken(token, tokenHashes)) return;
|
|
13223
|
+
if (yield* throttle.shouldRefuse) {
|
|
13224
|
+
const retryAfter = yield* throttle.recordFailure;
|
|
13225
|
+
yield* Effect.logWarning("refused hub request without checking; too many recent failures", { reason: token.length === 0 ? "missing_bearer_token" : "unknown_token" });
|
|
13226
|
+
return yield* Effect.fail(new HubThrottledError(retryAfter));
|
|
13141
13227
|
}
|
|
13228
|
+
yield* throttle.recordFailure;
|
|
13229
|
+
yield* Effect.logWarning("rejected hub request with an unusable credential", { reason: token.length === 0 ? "missing_bearer_token" : "unknown_token" });
|
|
13230
|
+
return yield* Effect.fail(new HubUnauthorizedError());
|
|
13142
13231
|
});
|
|
13143
13232
|
const SHA256_HEX = /^[0-9a-f]{64}$/;
|
|
13144
13233
|
/**
|
|
@@ -13547,6 +13636,7 @@ const storeUnavailable = HttpServerResponse.jsonUnsafe({
|
|
|
13547
13636
|
*/
|
|
13548
13637
|
const respondToHubFailures = (handler) => handler.pipe(Effect.catch((cause) => {
|
|
13549
13638
|
if (cause._tag === "HubUnauthorizedError") return Effect.succeed(hubUnauthorizedResponse);
|
|
13639
|
+
if (cause._tag === "HubThrottledError") return Effect.succeed(hubThrottledResponse(cause.retryAfterSeconds));
|
|
13550
13640
|
if (cause._tag === "InvalidHubRequest") return Effect.succeed(badRequest(cause.detail));
|
|
13551
13641
|
return Effect.logWarning("hub request failed", { cause }).pipe(Effect.as(storeUnavailable));
|
|
13552
13642
|
}));
|
|
@@ -13942,7 +14032,7 @@ const announce = Layer.effectDiscard(Effect.gen(function* () {
|
|
|
13942
14032
|
});
|
|
13943
14033
|
if (isRemoteReachableHost(config.host)) yield* Effect.logWarning("Hub is reachable beyond loopback. The bearer token is the only control — repeated auth failures are not rate limited.", { host: config.host });
|
|
13944
14034
|
}));
|
|
13945
|
-
const makeHubLayer = HttpRouter.serve(layer$58).pipe(Layer.provideMerge(announce), Layer.provideMerge(HubHttpServerLive), Layer.provideMerge(TaskRepositoryLive), Layer.provideMerge(AgentAssetRepositoryLive), Layer.provideMerge(HubPersistenceLive));
|
|
14035
|
+
const makeHubLayer = HttpRouter.serve(layer$58).pipe(Layer.provideMerge(announce), Layer.provideMerge(HubHttpServerLive), Layer.provideMerge(layer$59), Layer.provideMerge(TaskRepositoryLive), Layer.provideMerge(AgentAssetRepositoryLive), Layer.provideMerge(HubPersistenceLive));
|
|
13946
14036
|
const runHub = Layer.launch(makeHubLayer);
|
|
13947
14037
|
//#endregion
|
|
13948
14038
|
//#region src/atomicWrite.ts
|
|
@@ -16126,7 +16216,7 @@ const make$58 = Effect.gen(function* () {
|
|
|
16126
16216
|
const layer$53 = Layer.effect(AssetSync, make$58);
|
|
16127
16217
|
//#endregion
|
|
16128
16218
|
//#region src/cli/assets.ts
|
|
16129
|
-
const SyncServicesLive = layer$53.pipe(Layer.provideMerge(layer$54), Layer.provideMerge(layer$55), Layer.provideMerge(layer$57.pipe(Layer.provide(layer$
|
|
16219
|
+
const SyncServicesLive = layer$53.pipe(Layer.provideMerge(layer$54), Layer.provideMerge(layer$55), Layer.provideMerge(layer$57.pipe(Layer.provide(layer$64))), Layer.provideMerge(layer$64), Layer.provideMerge(FetchHttpClient.layer));
|
|
16130
16220
|
const runWithSync = (flags, run) => Effect.gen(function* () {
|
|
16131
16221
|
const config = yield* resolveCliAuthConfig(flags, yield* GlobalFlag.LogLevel);
|
|
16132
16222
|
return yield* Effect.gen(function* () {
|
|
@@ -16134,7 +16224,7 @@ const runWithSync = (flags, run) => Effect.gen(function* () {
|
|
|
16134
16224
|
sync: yield* AssetSync,
|
|
16135
16225
|
link: yield* HubLink
|
|
16136
16226
|
});
|
|
16137
|
-
}).pipe(Effect.provide(SyncServicesLive.pipe(Layer.provide(layer$
|
|
16227
|
+
}).pipe(Effect.provide(SyncServicesLive.pipe(Layer.provide(layer$67(config)), Layer.provide(Layer.succeed(References.MinimumLogLevel, "Error")))));
|
|
16138
16228
|
});
|
|
16139
16229
|
const STATE_LABEL = {
|
|
16140
16230
|
synced: "synced",
|
|
@@ -24098,6 +24188,92 @@ const removePinnedRuntimeInstallation = Effect.fn("cloud.pinned_runtime.remove")
|
|
|
24098
24188
|
cause
|
|
24099
24189
|
}))));
|
|
24100
24190
|
});
|
|
24191
|
+
//#endregion
|
|
24192
|
+
//#region src/service/serviceOptions.ts
|
|
24193
|
+
/**
|
|
24194
|
+
* The options a supervised server keeps between installs.
|
|
24195
|
+
*
|
|
24196
|
+
* The unit file is generated, not edited: `p4c service update` rewrites it on
|
|
24197
|
+
* every upgrade. So anything configured by hand in it — a bind host, Tailscale
|
|
24198
|
+
* Serve — disappears at the next update, silently, weeks after the person who
|
|
24199
|
+
* added it has forgotten it was there. That is a worse failure than not
|
|
24200
|
+
* supporting the option at all, because the machine works until it does not.
|
|
24201
|
+
*
|
|
24202
|
+
* These options are therefore stored beside the server's other state and read
|
|
24203
|
+
* back when the unit is rendered. `install`, `update` and `status` all go
|
|
24204
|
+
* through the same path, so an option set once survives every later upgrade,
|
|
24205
|
+
* and `status` notices when the file and the unit disagree — which is what
|
|
24206
|
+
* makes `update` rewrite the unit after an option changes.
|
|
24207
|
+
*
|
|
24208
|
+
* @module service/serviceOptions
|
|
24209
|
+
*/
|
|
24210
|
+
/**
|
|
24211
|
+
* Tailscale Serve puts the server on HTTPS at the machine's MagicDNS name.
|
|
24212
|
+
*
|
|
24213
|
+
* Worth being an option rather than a default: it publishes the server to
|
|
24214
|
+
* everything on the tailnet, which is a decision about exposure and not a
|
|
24215
|
+
* detail of how the service is launched.
|
|
24216
|
+
*/
|
|
24217
|
+
const TailscaleServeOptions = Schema$1.Struct({
|
|
24218
|
+
enabled: Schema$1.Boolean,
|
|
24219
|
+
port: Schema$1.Int.check(Schema$1.isGreaterThan(0), Schema$1.isLessThanOrEqualTo(65535))
|
|
24220
|
+
});
|
|
24221
|
+
const ServiceOptions = Schema$1.Struct({ tailscaleServe: Schema$1.optional(TailscaleServeOptions) });
|
|
24222
|
+
const EMPTY_SERVICE_OPTIONS = {};
|
|
24223
|
+
const OptionsFromJson = Schema$1.fromJsonString(ServiceOptions);
|
|
24224
|
+
const decodeOptions = Schema$1.decodeUnknownExit(OptionsFromJson);
|
|
24225
|
+
const encodeOptions = Schema$1.encodeSync(OptionsFromJson);
|
|
24226
|
+
const serviceOptionsPath = (input) => input.path.join(input.baseDir, "userdata", "service-options.json");
|
|
24227
|
+
/**
|
|
24228
|
+
* Read the stored options, or none.
|
|
24229
|
+
*
|
|
24230
|
+
* A missing file is the ordinary case — most machines never set an option — and
|
|
24231
|
+
* an unreadable or malformed one is treated the same way rather than failing
|
|
24232
|
+
* the install. The cost of ignoring it is a service that starts without an
|
|
24233
|
+
* option; the cost of failing is a machine with no service at all, and the
|
|
24234
|
+
* second is worse for a file nobody edits by hand.
|
|
24235
|
+
*/
|
|
24236
|
+
const readServiceOptions = Effect.fn("service.readServiceOptions")(function* (input) {
|
|
24237
|
+
const fileSystem = yield* FileSystem.FileSystem;
|
|
24238
|
+
const path = yield* Path.Path;
|
|
24239
|
+
const contents = yield* fileSystem.readFileString(serviceOptionsPath({
|
|
24240
|
+
baseDir: input.baseDir,
|
|
24241
|
+
path
|
|
24242
|
+
})).pipe(Effect.orElseSucceed(() => void 0));
|
|
24243
|
+
if (contents === void 0) return EMPTY_SERVICE_OPTIONS;
|
|
24244
|
+
const decoded = decodeOptions(contents);
|
|
24245
|
+
return decoded._tag === "Success" ? decoded.value : EMPTY_SERVICE_OPTIONS;
|
|
24246
|
+
});
|
|
24247
|
+
const writeServiceOptions = Effect.fn("service.writeServiceOptions")(function* (input) {
|
|
24248
|
+
const fileSystem = yield* FileSystem.FileSystem;
|
|
24249
|
+
const path = yield* Path.Path;
|
|
24250
|
+
const filePath = serviceOptionsPath({
|
|
24251
|
+
baseDir: input.baseDir,
|
|
24252
|
+
path
|
|
24253
|
+
});
|
|
24254
|
+
yield* fileSystem.makeDirectory(path.dirname(filePath), { recursive: true }).pipe(Effect.ignore);
|
|
24255
|
+
yield* writeFileStringAtomically({
|
|
24256
|
+
filePath,
|
|
24257
|
+
contents: `${encodeOptions(input.options)}\n`
|
|
24258
|
+
});
|
|
24259
|
+
});
|
|
24260
|
+
/**
|
|
24261
|
+
* The environment the unit sets, derived from the options.
|
|
24262
|
+
*
|
|
24263
|
+
* Pairs rather than a record so the rendered unit is byte-stable: a record's
|
|
24264
|
+
* iteration order is stable in practice but nothing says so, and `status`
|
|
24265
|
+
* compares the rendered unit against the installed one to decide whether an
|
|
24266
|
+
* update is needed. A reordering would read as a change forever.
|
|
24267
|
+
*/
|
|
24268
|
+
const serviceEnvironmentFor = (options) => {
|
|
24269
|
+
const pairs = [];
|
|
24270
|
+
const tailscale = options.tailscaleServe;
|
|
24271
|
+
if (tailscale?.enabled === true) {
|
|
24272
|
+
pairs.push(["P4CODE_TAILSCALE_SERVE", "1"]);
|
|
24273
|
+
if (tailscale.port !== 443) pairs.push(["P4CODE_TAILSCALE_SERVE_PORT", String(tailscale.port)]);
|
|
24274
|
+
}
|
|
24275
|
+
return pairs;
|
|
24276
|
+
};
|
|
24101
24277
|
const BOOT_SERVICE_UNIT_FILE = `p4code.service`;
|
|
24102
24278
|
const BOOT_SERVICE_UNIT_ENV = "P4_BOOT_SERVICE_UNIT";
|
|
24103
24279
|
/**
|
|
@@ -24158,6 +24334,7 @@ function renderBootServiceUnit(plan) {
|
|
|
24158
24334
|
"WorkingDirectory=%h",
|
|
24159
24335
|
`Environment=P4CODE_HOME=${quoteSystemdValue(plan.baseDir)}`,
|
|
24160
24336
|
`Environment=${BOOT_SERVICE_UNIT_ENV}=${BOOT_SERVICE_UNIT_FILE}`,
|
|
24337
|
+
...plan.serviceEnvironment.map(([name, value]) => `Environment=${name}=${quoteSystemdValue(value)}`),
|
|
24161
24338
|
`ExecStart=${quoteSystemdValue(plan.nodePath)} ${quoteSystemdValue(plan.p4EntryPath)} serve`,
|
|
24162
24339
|
"Restart=always",
|
|
24163
24340
|
"RestartSec=5",
|
|
@@ -24204,6 +24381,7 @@ function renderLaunchAgentPlist(plan) {
|
|
|
24204
24381
|
` <string>${escapeXmlText(plan.baseDir)}</string>`,
|
|
24205
24382
|
` <key>${escapeXmlText(BOOT_SERVICE_UNIT_ENV)}</key>`,
|
|
24206
24383
|
` <string>${escapeXmlText(LAUNCH_AGENT_PLIST_FILE)}</string>`,
|
|
24384
|
+
...plan.serviceEnvironment.flatMap(([name, value]) => [` <key>${escapeXmlText(name)}</key>`, ` <string>${escapeXmlText(value)}</string>`]),
|
|
24207
24385
|
" </dict>",
|
|
24208
24386
|
" <key>RunAtLoad</key>",
|
|
24209
24387
|
" <true/>",
|
|
@@ -24332,13 +24510,15 @@ const make$51 = Effect.fn("cloud.boot_service.make")(function* (input) {
|
|
|
24332
24510
|
}) : new BootServiceInstallError({ cause: error })), Effect.tapError((error) => DateTime.now.pipe(Effect.flatMap((now) => fs.writeFileString(logPath, `${DateTime.formatIso(now)} ${error.message}\n`, { flag: "a" })), Effect.ignore)));
|
|
24333
24511
|
});
|
|
24334
24512
|
const plannedEntryPath = isEphemeralCacheEntry(host.cliEntryPath) ? runtimePaths.entryPath : host.cliEntryPath;
|
|
24513
|
+
const serviceOptions = yield* readServiceOptions({ baseDir: input.baseDir });
|
|
24335
24514
|
const plan = {
|
|
24336
24515
|
nodePath: host.execPath,
|
|
24337
24516
|
p4EntryPath: plannedEntryPath,
|
|
24338
24517
|
baseDir: input.baseDir,
|
|
24339
24518
|
logPath,
|
|
24340
24519
|
unitPath,
|
|
24341
|
-
workingDirectory: homeDir
|
|
24520
|
+
workingDirectory: homeDir,
|
|
24521
|
+
serviceEnvironment: serviceEnvironmentFor(serviceOptions)
|
|
24342
24522
|
};
|
|
24343
24523
|
const install = Effect.gen(function* () {
|
|
24344
24524
|
yield* requireSupportedPlatform;
|
|
@@ -24492,7 +24672,7 @@ const RESTART_DELAY = Duration.seconds(2);
|
|
|
24492
24672
|
/** Exact npm versions only — never dist-tags — so the acknowledgement names
|
|
24493
24673
|
the version that was actually installed. Also keeps the value safe to
|
|
24494
24674
|
pass to npm and embed in filesystem paths. */
|
|
24495
|
-
const EXACT_VERSION_PATTERN = /^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?$/;
|
|
24675
|
+
const EXACT_VERSION_PATTERN$1 = /^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?$/;
|
|
24496
24676
|
function normalizeEntryPath(entryPath) {
|
|
24497
24677
|
return entryPath.replaceAll("\\", "/");
|
|
24498
24678
|
}
|
|
@@ -24608,7 +24788,7 @@ const make$50 = Effect.fn("cloud.server_self_update.make")(function* (options) {
|
|
|
24608
24788
|
if (capability === null) return yield* failWith("This server cannot update itself; relaunch it manually with the new version.");
|
|
24609
24789
|
const activeMethod = capability;
|
|
24610
24790
|
const targetVersion = input.targetVersion.trim();
|
|
24611
|
-
if (!EXACT_VERSION_PATTERN.test(targetVersion)) return yield* failWith(`'${targetVersion}' is not an exact p4 version.`);
|
|
24791
|
+
if (!EXACT_VERSION_PATTERN$1.test(targetVersion)) return yield* failWith(`'${targetVersion}' is not an exact p4 version.`);
|
|
24612
24792
|
if (yield* Ref.getAndSet(inFlight, true)) return yield* failWith("A server update is already in progress.");
|
|
24613
24793
|
return yield* Effect.gen(function* () {
|
|
24614
24794
|
const runtimePaths = yield* ensurePinnedRuntimeInstalled({
|
|
@@ -24637,13 +24817,15 @@ const make$50 = Effect.fn("cloud.server_self_update.make")(function* (options) {
|
|
|
24637
24817
|
const homeDir = env.HOME ?? "";
|
|
24638
24818
|
const unitPath = path.join(homeDir, ".config", "systemd", "user", BOOT_SERVICE_UNIT_FILE);
|
|
24639
24819
|
const previousUnit = yield* fs.readFileString(unitPath).pipe(Effect.mapError((cause) => failWith("Could not read the current systemd unit.", cause)));
|
|
24820
|
+
const serviceOptions = yield* readServiceOptions({ baseDir: serverConfig.baseDir }).pipe(Effect.provideService(FileSystem.FileSystem, fs), Effect.provideService(Path.Path, path));
|
|
24640
24821
|
const unit = renderBootServiceUnit({
|
|
24641
24822
|
nodePath: host.execPath,
|
|
24642
24823
|
p4EntryPath: runtimePaths.entryPath,
|
|
24643
24824
|
baseDir: serverConfig.baseDir,
|
|
24644
24825
|
logPath: path.join(serverConfig.logsDir, "boot-service.log"),
|
|
24645
24826
|
unitPath,
|
|
24646
|
-
workingDirectory: homeDir
|
|
24827
|
+
workingDirectory: homeDir,
|
|
24828
|
+
serviceEnvironment: serviceEnvironmentFor(serviceOptions)
|
|
24647
24829
|
});
|
|
24648
24830
|
yield* writeUnitAtomically(unitPath, unit).pipe(Effect.mapError((cause) => failWith("Could not update the systemd unit.", cause)));
|
|
24649
24831
|
const reloadSystemd = Effect.fn("cloud.server_self_update.reload_systemd")(function* () {
|
|
@@ -26512,7 +26694,7 @@ const runProjectMutation = Effect.fn("runProjectMutation")(function* (flags, run
|
|
|
26512
26694
|
});
|
|
26513
26695
|
yield* Console.log(output);
|
|
26514
26696
|
}));
|
|
26515
|
-
const offlineRuntimeLayer = ProjectCliRuntimeLive.pipe(Layer.provide(layer$
|
|
26697
|
+
const offlineRuntimeLayer = ProjectCliRuntimeLive.pipe(Layer.provide(layer$67(config)), Layer.provide(Layer.succeed(References.MinimumLogLevel, minimumLogLevel)));
|
|
26516
26698
|
return yield* Effect.gen(function* () {
|
|
26517
26699
|
const snapshot = yield* getOfflineSnapshot();
|
|
26518
26700
|
const orchestrationEngine = yield* OrchestrationEngineService;
|
|
@@ -26523,7 +26705,7 @@ const runProjectMutation = Effect.fn("runProjectMutation")(function* (flags, run
|
|
|
26523
26705
|
});
|
|
26524
26706
|
yield* Console.log(output);
|
|
26525
26707
|
}).pipe(Effect.provide(offlineRuntimeLayer));
|
|
26526
|
-
}).pipe(Effect.provide(Layer.mergeAll(runtimeLayer, layer$42).pipe(Layer.provideMerge(FetchHttpClient.layer), Layer.provide(layer$
|
|
26708
|
+
}).pipe(Effect.provide(Layer.mergeAll(runtimeLayer, layer$42).pipe(Layer.provideMerge(FetchHttpClient.layer), Layer.provide(layer$67(config)), Layer.provide(Layer.succeed(References.MinimumLogLevel, minimumLogLevel)))));
|
|
26527
26709
|
});
|
|
26528
26710
|
const projectAddCommand = Command.make("add", {
|
|
26529
26711
|
...projectLocationFlags,
|
|
@@ -43519,7 +43701,7 @@ const makeOpenCodeRuntime = Effect.gen(function* () {
|
|
|
43519
43701
|
};
|
|
43520
43702
|
});
|
|
43521
43703
|
var OpenCodeRuntime = class extends Context.Service()("@p4code/cli/provider/opencodeRuntime") {};
|
|
43522
|
-
const OpenCodeRuntimeLive = Layer.effect(OpenCodeRuntime, makeOpenCodeRuntime).pipe(Layer.provide(layer$
|
|
43704
|
+
const OpenCodeRuntimeLive = Layer.effect(OpenCodeRuntime, makeOpenCodeRuntime).pipe(Layer.provide(layer$68));
|
|
43523
43705
|
//#endregion
|
|
43524
43706
|
//#region src/textGeneration/TextGenerationUtils.ts
|
|
43525
43707
|
const isTextGenerationError$2 = Schema$1.is(TextGenerationError);
|
|
@@ -82612,9 +82794,9 @@ const WorkspaceEntriesLayerLive = layer$26.pipe(Layer.provide(layer$42));
|
|
|
82612
82794
|
const WorkspaceFileSystemLayerLive = layer$25.pipe(Layer.provide(layer$42), Layer.provide(WorkspaceEntriesLayerLive));
|
|
82613
82795
|
const WorkspaceLayerLive = Layer.mergeAll(layer$42, WorkspaceEntriesLayerLive, WorkspaceFileSystemLayerLive);
|
|
82614
82796
|
const ProjectFaviconResolverLayerLive = layer$40.pipe(Layer.provide(layer$42), Layer.provide(layer$41));
|
|
82615
|
-
const AuthLayerLive = layer$
|
|
82797
|
+
const AuthLayerLive = layer$60.pipe(Layer.provideMerge(PersistenceLayerLive), Layer.provide(layer$64));
|
|
82616
82798
|
const ProviderRuntimeLayerLive = ProviderSessionReaperLive.pipe(Layer.provideMerge(ProviderLayerLive), Layer.provideMerge(OrchestrationLayerLive));
|
|
82617
|
-
const RuntimeDependenciesLive = ReactorLayerLive.pipe(Layer.provideMerge(CheckpointingLayerLive), Layer.provideMerge(SourceControlProviderRegistryLayerLive), Layer.provideMerge(GitLayerLive), Layer.provideMerge(VcsLayerLive), Layer.provideMerge(ProviderRuntimeLayerLive), Layer.provideMerge(Layer.mergeAll(TerminalLayerLive, PreviewLayerLive)), Layer.provideMerge(PersistenceLayerLive), Layer.provideMerge(layer$50), Layer.provideMerge(ProviderRegistryLive), Layer.provideMerge(ProviderInstanceRegistryHydrationLive), Layer.provideMerge(ProviderEventLoggersLive), Layer.provideMerge(OpenCodeRuntimeLive), Layer.provideMerge(layer$57.pipe(Layer.provide(layer$
|
|
82799
|
+
const RuntimeDependenciesLive = ReactorLayerLive.pipe(Layer.provideMerge(CheckpointingLayerLive), Layer.provideMerge(SourceControlProviderRegistryLayerLive), Layer.provideMerge(GitLayerLive), Layer.provideMerge(VcsLayerLive), Layer.provideMerge(ProviderRuntimeLayerLive), Layer.provideMerge(Layer.mergeAll(TerminalLayerLive, PreviewLayerLive)), Layer.provideMerge(PersistenceLayerLive), Layer.provideMerge(layer$50), Layer.provideMerge(ProviderRegistryLive), Layer.provideMerge(ProviderInstanceRegistryHydrationLive), Layer.provideMerge(ProviderEventLoggersLive), Layer.provideMerge(OpenCodeRuntimeLive), Layer.provideMerge(layer$57.pipe(Layer.provide(layer$64))), Layer.provideMerge(WorkspaceLayerLive), Layer.provideMerge(ProjectFaviconResolverLayerLive), Layer.provideMerge(layer$51), Layer.provideMerge(layer$44), Layer.provideMerge(AuthLayerLive), Layer.provideMerge(layer$56), Layer.provideMerge(layer$64)).pipe(Layer.provideMerge(layer$8), Layer.provideMerge(layer$7), Layer.provideMerge(layer$6), Layer.provideMerge(layer$47), Layer.provideMerge(layer$49), Layer.provideMerge(layer$48), Layer.provide(layer$68));
|
|
82618
82800
|
/**
|
|
82619
82801
|
* Hub asset sync.
|
|
82620
82802
|
*
|
|
@@ -82682,6 +82864,151 @@ const serveCommand = Command.make("serve", { ...sharedServerCommandFlags }).pipe
|
|
|
82682
82864
|
forceAutoBootstrapProjectFromCwd: false
|
|
82683
82865
|
})));
|
|
82684
82866
|
//#endregion
|
|
82867
|
+
//#region src/service/cliSelfUpdate.ts
|
|
82868
|
+
/**
|
|
82869
|
+
* Upgrading the globally installed CLI, then the service that runs it.
|
|
82870
|
+
*
|
|
82871
|
+
* Two steps that have to happen in that order and from different binaries, and
|
|
82872
|
+
* getting the order wrong is silent: `service update` renders the unit from the
|
|
82873
|
+
* *running* CLI, and pins that version into the runtime directory. Run the
|
|
82874
|
+
* reconcile from the old process and you install a new CLI and then carefully
|
|
82875
|
+
* point the service back at the old one.
|
|
82876
|
+
*
|
|
82877
|
+
* So the second step re-executes the newly installed binary. That is also why
|
|
82878
|
+
* this is not `ServerSelfUpdate`: that path respawns the server in place, which
|
|
82879
|
+
* it explicitly refuses to do under launchd — KeepAlive would restart the
|
|
82880
|
+
* process being replaced and leave two servers on one port. Under a supervisor
|
|
82881
|
+
* the honest sequence is install, then let the supervisor restart, which is
|
|
82882
|
+
* exactly what `service update` does.
|
|
82883
|
+
*
|
|
82884
|
+
* @module service/cliSelfUpdate
|
|
82885
|
+
*/
|
|
82886
|
+
/** npm can take minutes when a native dependency compiles from source. */
|
|
82887
|
+
const CLI_INSTALL_TIMEOUT_MS = 600 * 1e3;
|
|
82888
|
+
const PACKAGE_NAME = "@p4code/cli";
|
|
82889
|
+
/**
|
|
82890
|
+
* Exact versions or the two dist-tags, and nothing else.
|
|
82891
|
+
*
|
|
82892
|
+
* The value is handed to npm and printed back as "installed X". An arbitrary
|
|
82893
|
+
* string could be a git URL or a local path, which npm would happily install
|
|
82894
|
+
* and which would make the acknowledgement a lie.
|
|
82895
|
+
*/
|
|
82896
|
+
const EXACT_VERSION_PATTERN = /^\d+\.\d+\.\d+(?:-[0-9A-Za-z-.]+)?$/u;
|
|
82897
|
+
const isInstallableVersion = (value) => value === "latest" || value === "next" || EXACT_VERSION_PATTERN.test(value);
|
|
82898
|
+
var CliSelfUpdateError = class extends Schema$1.TaggedErrorClass()("CliSelfUpdateError", {
|
|
82899
|
+
step: Schema$1.String,
|
|
82900
|
+
detail: Schema$1.optional(Schema$1.String)
|
|
82901
|
+
}) {
|
|
82902
|
+
get message() {
|
|
82903
|
+
return this.detail === void 0 ? `Self-update failed while ${this.step}.` : `Self-update failed while ${this.step}: ${this.detail}`;
|
|
82904
|
+
}
|
|
82905
|
+
};
|
|
82906
|
+
/**
|
|
82907
|
+
* Where `npm install --global` put the executable.
|
|
82908
|
+
*
|
|
82909
|
+
* Asked of npm rather than assumed, because the answer differs between a
|
|
82910
|
+
* Homebrew node, an nvm one and a system one — and the whole point of this step
|
|
82911
|
+
* is to run the binary that was just written, not the one on this process's
|
|
82912
|
+
* PATH, which a shell may have cached.
|
|
82913
|
+
*/
|
|
82914
|
+
const resolveGlobalBinary = Effect.fn("cli.selfUpdate.resolveGlobalBinary")(function* (binaryName) {
|
|
82915
|
+
const result = yield* (yield* ProcessRunner).run({
|
|
82916
|
+
command: "npm",
|
|
82917
|
+
args: ["prefix", "--global"]
|
|
82918
|
+
}).pipe(Effect.mapError((cause) => new CliSelfUpdateError({
|
|
82919
|
+
step: "locating the npm global prefix",
|
|
82920
|
+
detail: String(cause)
|
|
82921
|
+
})));
|
|
82922
|
+
if (result.code !== 0) return yield* new CliSelfUpdateError({
|
|
82923
|
+
step: "locating the npm global prefix",
|
|
82924
|
+
detail: `npm exited with code ${String(result.code)}`
|
|
82925
|
+
});
|
|
82926
|
+
const prefix = result.stdout.trim();
|
|
82927
|
+
if (prefix.length === 0) return yield* new CliSelfUpdateError({
|
|
82928
|
+
step: "locating the npm global prefix",
|
|
82929
|
+
detail: "npm printed no prefix"
|
|
82930
|
+
});
|
|
82931
|
+
return `${prefix}/bin/${binaryName}`;
|
|
82932
|
+
});
|
|
82933
|
+
const installGlobalCli = Effect.fn("cli.selfUpdate.installGlobalCli")(function* (input) {
|
|
82934
|
+
const runner = yield* ProcessRunner;
|
|
82935
|
+
const step = `installing ${PACKAGE_NAME}@${input.version}`;
|
|
82936
|
+
const result = yield* runner.run({
|
|
82937
|
+
command: "npm",
|
|
82938
|
+
args: [
|
|
82939
|
+
"install",
|
|
82940
|
+
"--global",
|
|
82941
|
+
"--no-fund",
|
|
82942
|
+
"--no-audit",
|
|
82943
|
+
`${PACKAGE_NAME}@${input.version}`
|
|
82944
|
+
],
|
|
82945
|
+
timeout: CLI_INSTALL_TIMEOUT_MS
|
|
82946
|
+
}).pipe(Effect.mapError((cause) => new CliSelfUpdateError({
|
|
82947
|
+
step,
|
|
82948
|
+
detail: String(cause)
|
|
82949
|
+
})));
|
|
82950
|
+
if (result.code !== 0) return yield* new CliSelfUpdateError({
|
|
82951
|
+
step,
|
|
82952
|
+
detail: result.stderr.trim().length > 0 ? result.stderr.trim() : `exit code ${String(result.code)}`
|
|
82953
|
+
});
|
|
82954
|
+
});
|
|
82955
|
+
/**
|
|
82956
|
+
* Ask the freshly installed binary what version it is.
|
|
82957
|
+
*
|
|
82958
|
+
* A completed npm install can still be unusable — a native dependency that
|
|
82959
|
+
* failed to build, a Node too old — and it is better to find that out here,
|
|
82960
|
+
* while the service is still running the version that works, than after
|
|
82961
|
+
* pointing the unit at it.
|
|
82962
|
+
*/
|
|
82963
|
+
const readInstalledVersion = Effect.fn("cli.selfUpdate.readInstalledVersion")(function* (binaryPath) {
|
|
82964
|
+
const runner = yield* ProcessRunner;
|
|
82965
|
+
const step = "verifying the installed CLI";
|
|
82966
|
+
const result = yield* runner.run({
|
|
82967
|
+
command: binaryPath,
|
|
82968
|
+
args: ["--version"]
|
|
82969
|
+
}).pipe(Effect.mapError((cause) => new CliSelfUpdateError({
|
|
82970
|
+
step,
|
|
82971
|
+
detail: String(cause)
|
|
82972
|
+
})));
|
|
82973
|
+
if (result.code !== 0) return yield* new CliSelfUpdateError({
|
|
82974
|
+
step,
|
|
82975
|
+
detail: `the installed CLI exited with code ${String(result.code)}`
|
|
82976
|
+
});
|
|
82977
|
+
const match = /(\d+\.\d+\.\d+(?:-[0-9A-Za-z-.]+)?)/u.exec(result.stdout);
|
|
82978
|
+
if (match?.[1] === void 0) return yield* new CliSelfUpdateError({
|
|
82979
|
+
step,
|
|
82980
|
+
detail: "the installed CLI did not report a version"
|
|
82981
|
+
});
|
|
82982
|
+
return match[1];
|
|
82983
|
+
});
|
|
82984
|
+
/**
|
|
82985
|
+
* Hand the rest of the work to the new binary.
|
|
82986
|
+
*
|
|
82987
|
+
* `service update` from *this* process would pin this process's version. The
|
|
82988
|
+
* new binary reconciles to itself, which is the only way the unit ends up
|
|
82989
|
+
* pointing at what was just installed.
|
|
82990
|
+
*/
|
|
82991
|
+
const reconcileWithNewBinary = Effect.fn("cli.selfUpdate.reconcileWithNewBinary")(function* (input) {
|
|
82992
|
+
const runner = yield* ProcessRunner;
|
|
82993
|
+
const step = "updating the service to the new version";
|
|
82994
|
+
const result = yield* runner.run({
|
|
82995
|
+
command: input.binaryPath,
|
|
82996
|
+
args: [
|
|
82997
|
+
"service",
|
|
82998
|
+
"update",
|
|
82999
|
+
...input.baseDir === void 0 ? [] : ["--base-dir", input.baseDir]
|
|
83000
|
+
],
|
|
83001
|
+
timeout: CLI_INSTALL_TIMEOUT_MS
|
|
83002
|
+
}).pipe(Effect.mapError((cause) => new CliSelfUpdateError({
|
|
83003
|
+
step,
|
|
83004
|
+
detail: String(cause)
|
|
83005
|
+
})));
|
|
83006
|
+
if (result.code !== 0) return yield* new CliSelfUpdateError({
|
|
83007
|
+
step,
|
|
83008
|
+
detail: result.stderr.trim().length > 0 ? result.stderr.trim() : `exit code ${String(result.code)}`
|
|
83009
|
+
});
|
|
83010
|
+
});
|
|
83011
|
+
//#endregion
|
|
82685
83012
|
//#region src/cli/service.ts
|
|
82686
83013
|
const isBootServiceCommandError = Schema$1.is(BootServiceCommandError);
|
|
82687
83014
|
const bootServiceLayer = (config) => layer$46({
|
|
@@ -82730,13 +83057,51 @@ function formatServiceStatus(status, cliVersion) {
|
|
|
82730
83057
|
...status.current ? [] : [" Next: Run `p4c service update`."]
|
|
82731
83058
|
].join("\n");
|
|
82732
83059
|
}
|
|
82733
|
-
const runServiceCommand = Effect.fn("cli.service.run")(function* (flags, run) {
|
|
83060
|
+
const runServiceCommand = Effect.fn("cli.service.run")(function* (flags, run, prepare) {
|
|
82734
83061
|
const config = yield* resolveCliAuthConfig(flags, yield* GlobalFlag.LogLevel);
|
|
82735
83062
|
const baseDirNotice = yield* describeNonDefaultBaseDir(config.baseDir);
|
|
82736
83063
|
if (baseDirNotice !== null) yield* Console.error(baseDirNotice);
|
|
83064
|
+
if (prepare !== void 0) yield* prepare(config);
|
|
82737
83065
|
return yield* run.pipe(Effect.tapError((error) => isBootServiceCommandError(error) ? BootService.pipe(Effect.flatMap((service) => Console.error(`Details: ${service.logPath}`))) : Effect.void), Effect.provide(Layer.mergeAll(bootServiceLayer(config), layer$52)));
|
|
82738
83066
|
});
|
|
82739
|
-
|
|
83067
|
+
/**
|
|
83068
|
+
* Options the installed unit carries, rather than ones this command consumes.
|
|
83069
|
+
*
|
|
83070
|
+
* Optional on purpose. Absent means "leave whatever is stored alone", so
|
|
83071
|
+
* re-running install to pick up a new CLI build does not quietly switch
|
|
83072
|
+
* Tailscale Serve off — which a plain boolean flag, false when unspecified,
|
|
83073
|
+
* would do every time.
|
|
83074
|
+
*/
|
|
83075
|
+
const tailscaleServeFlag = Flag.boolean("tailscale-serve").pipe(Flag.withDescription("Expose this server over HTTPS on your tailnet at its MagicDNS name. Use --no-tailscale-serve to turn it off."), Flag.optional);
|
|
83076
|
+
const tailscaleServePortFlag = Flag.integer("tailscale-serve-port").pipe(Flag.withDescription(`HTTPS port for Tailscale Serve (default 443).`), Flag.optional);
|
|
83077
|
+
/**
|
|
83078
|
+
* Fold the flags into what is already stored, and persist.
|
|
83079
|
+
*
|
|
83080
|
+
* Written before the unit is rendered, because the renderer reads this file —
|
|
83081
|
+
* so `install` and `update` both produce a unit that matches, and `status`
|
|
83082
|
+
* compares against the same thing.
|
|
83083
|
+
*/
|
|
83084
|
+
const applyServiceOptionFlags = Effect.fn("cli.service.applyOptionFlags")(function* (input) {
|
|
83085
|
+
if (Option.isNone(input.tailscaleServe) && Option.isNone(input.tailscaleServePort)) return;
|
|
83086
|
+
const stored = yield* readServiceOptions({ baseDir: input.baseDir });
|
|
83087
|
+
const enabled = Option.getOrElse(input.tailscaleServe, () => stored.tailscaleServe?.enabled ?? false);
|
|
83088
|
+
const port = Option.getOrElse(input.tailscaleServePort, () => stored.tailscaleServe?.port ?? 443);
|
|
83089
|
+
yield* writeServiceOptions({
|
|
83090
|
+
baseDir: input.baseDir,
|
|
83091
|
+
options: {
|
|
83092
|
+
...stored,
|
|
83093
|
+
tailscaleServe: {
|
|
83094
|
+
enabled,
|
|
83095
|
+
port
|
|
83096
|
+
}
|
|
83097
|
+
}
|
|
83098
|
+
});
|
|
83099
|
+
});
|
|
83100
|
+
const serviceInstallCommand = Command.make("install", {
|
|
83101
|
+
...projectLocationFlags,
|
|
83102
|
+
tailscaleServe: tailscaleServeFlag,
|
|
83103
|
+
tailscaleServePort: tailscaleServePortFlag
|
|
83104
|
+
}).pipe(Command.withDescription("Install P4Code as a background service for this user."), Command.withHandler((flags) => runServiceCommand(flags, Effect.gen(function* () {
|
|
82740
83105
|
const result = yield* reconcileService();
|
|
82741
83106
|
if (!result.changed) {
|
|
82742
83107
|
yield* Console.log(`P4Code service is already installed with @p4code/cli@${version}.`);
|
|
@@ -82748,6 +83113,10 @@ const serviceInstallCommand = Command.make("install", projectLocationFlags).pipe
|
|
|
82748
83113
|
cliVersion: version,
|
|
82749
83114
|
platform: yield* HostProcessPlatform
|
|
82750
83115
|
}));
|
|
83116
|
+
}), (config) => applyServiceOptionFlags({
|
|
83117
|
+
baseDir: config.baseDir,
|
|
83118
|
+
tailscaleServe: flags.tailscaleServe,
|
|
83119
|
+
tailscaleServePort: flags.tailscaleServePort
|
|
82751
83120
|
}))));
|
|
82752
83121
|
const serviceUpdateCommand = Command.make("update", projectLocationFlags).pipe(Command.withDescription("Update or repair the background service so it runs this CLI build, then restart it."), Command.withHandler((flags) => runServiceCommand(flags, Effect.gen(function* () {
|
|
82753
83122
|
const result = yield* reconcileService();
|
|
@@ -82767,6 +83136,57 @@ const serviceUninstallCommand = Command.make("uninstall", projectLocationFlags).
|
|
|
82767
83136
|
const removed = yield* (yield* BootService).uninstall;
|
|
82768
83137
|
yield* Console.log(removed ? "Removed the P4Code service." : "P4Code service is not installed.");
|
|
82769
83138
|
}))));
|
|
83139
|
+
/**
|
|
83140
|
+
* Restart without rewriting the unit.
|
|
83141
|
+
*
|
|
83142
|
+
* `update` restarts too, but only after deciding the unit is out of date — and
|
|
83143
|
+
* a global `npm i -g` upgrades in place, so the rendered unit is byte-identical
|
|
83144
|
+
* and `update` correctly reports there is nothing to do while the running
|
|
83145
|
+
* process still holds the old code. The service already knew how to do this;
|
|
83146
|
+
* only the terminal had no way to ask.
|
|
83147
|
+
*/
|
|
83148
|
+
const serviceRestartCommand = Command.make("restart", projectLocationFlags).pipe(Command.withDescription("Restart the background service without changing its configuration."), Command.withHandler((flags) => runServiceCommand(flags, Effect.gen(function* () {
|
|
83149
|
+
const service = yield* BootService;
|
|
83150
|
+
if (!(yield* service.status).installed) {
|
|
83151
|
+
yield* Console.error("P4Code service is not installed. Install it with: p4c service install");
|
|
83152
|
+
return;
|
|
83153
|
+
}
|
|
83154
|
+
yield* service.restart;
|
|
83155
|
+
yield* Console.log("Restarted the P4Code service.");
|
|
83156
|
+
}))));
|
|
83157
|
+
/**
|
|
83158
|
+
* Install a newer CLI, then point the service at it.
|
|
83159
|
+
*
|
|
83160
|
+
* Deliberately not `ServerSelfUpdate`, which the server uses to replace itself
|
|
83161
|
+
* in place: that path refuses to run under launchd, because KeepAlive would
|
|
83162
|
+
* restart the process being replaced and leave two servers on one port. Under a
|
|
83163
|
+
* supervisor the honest sequence is install, then reconcile and let the
|
|
83164
|
+
* supervisor restart — which is what this does, from the new binary.
|
|
83165
|
+
*/
|
|
83166
|
+
const serviceSelfUpdateCommand = Command.make("self-update", {
|
|
83167
|
+
...projectLocationFlags,
|
|
83168
|
+
version: Flag.string("version").pipe(Flag.withDescription("Version or dist-tag to install (default latest)."), Flag.optional)
|
|
83169
|
+
}).pipe(Command.withDescription("Install the newest @p4code/cli and update the service to run it."), Command.withHandler((flags) => runServiceCommand(flags, Effect.gen(function* () {
|
|
83170
|
+
const requested = Option.getOrElse(flags.version, () => "latest");
|
|
83171
|
+
if (!isInstallableVersion(requested)) {
|
|
83172
|
+
yield* Console.error(`'${requested}' is not a version or dist-tag. Use an exact version, or latest.`);
|
|
83173
|
+
return;
|
|
83174
|
+
}
|
|
83175
|
+
yield* Console.log(`Installing ${PACKAGE_NAME}@${requested}…`);
|
|
83176
|
+
yield* installGlobalCli({ version: requested });
|
|
83177
|
+
const binaryPath = yield* resolveGlobalBinary("p4c");
|
|
83178
|
+
const installed = yield* readInstalledVersion(binaryPath);
|
|
83179
|
+
if (installed === version) {
|
|
83180
|
+
yield* Console.log(`Already running @p4code/cli@${installed}; nothing to update.`);
|
|
83181
|
+
return;
|
|
83182
|
+
}
|
|
83183
|
+
yield* Console.log(`Installed @p4code/cli@${installed}; updating the service…`);
|
|
83184
|
+
yield* reconcileWithNewBinary({
|
|
83185
|
+
binaryPath,
|
|
83186
|
+
baseDir: void 0
|
|
83187
|
+
});
|
|
83188
|
+
yield* Console.log(`Service now runs @p4code/cli@${installed}.`);
|
|
83189
|
+
}))));
|
|
82770
83190
|
const serviceStatusCommand = Command.make("status", projectLocationFlags).pipe(Command.withDescription("Show whether the P4Code background service is installed."), Command.withHandler((flags) => runServiceCommand(flags, Effect.gen(function* () {
|
|
82771
83191
|
const service = yield* BootService;
|
|
82772
83192
|
yield* Console.log(formatServiceStatus(yield* service.status, version));
|
|
@@ -82790,11 +83210,13 @@ const serviceCommand = Command.make("service").pipe(Command.withDescription("Man
|
|
|
82790
83210
|
serviceInstallCommand,
|
|
82791
83211
|
serviceUninstallCommand,
|
|
82792
83212
|
serviceUpdateCommand,
|
|
83213
|
+
serviceRestartCommand,
|
|
83214
|
+
serviceSelfUpdateCommand,
|
|
82793
83215
|
serviceStatusCommand
|
|
82794
83216
|
]));
|
|
82795
83217
|
//#endregion
|
|
82796
83218
|
//#region src/bin.ts
|
|
82797
|
-
const CliRuntimeLayer = Layer.mergeAll(NodeServices.layer, layer$
|
|
83219
|
+
const CliRuntimeLayer = Layer.mergeAll(NodeServices.layer, layer$68, FetchHttpClient.layer);
|
|
82798
83220
|
/**
|
|
82799
83221
|
* Prints an update reminder before dispatching, at most once a day.
|
|
82800
83222
|
*
|