@p4code/cli 0.0.36 → 0.0.38
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/{NodeSqliteClient-BUlNQAY0.mjs → NodeSqliteClient-Dq296EBh.mjs} +2 -2
- package/dist/bin.mjs +441 -372
- package/dist/client/assets/{DiffPanel-DKJ8ECjA.js → DiffPanel-D0CF-iwb.js} +2 -2
- package/dist/client/assets/{FilePreviewPanel-DrEwfDlH.js → FilePreviewPanel-BUGKMTB4.js} +2 -2
- package/dist/client/assets/{PreviewPanel-DWT1ePi1.js → PreviewPanel-Lk9ar7c8.js} +2 -2
- package/dist/client/assets/{fileCommentAnnotations-QSDzusB8.js → fileCommentAnnotations-BNNcqDCJ.js} +2 -2
- package/dist/client/assets/{index-BSt_6F_c.js → index-CmXGS7F0.js} +4 -4
- package/dist/client/index.html +1 -1
- package/package.json +1 -1
package/dist/bin.mjs
CHANGED
|
@@ -54,6 +54,9 @@ import * as Exit from "effect/Exit";
|
|
|
54
54
|
import * as SchemaGetter from "effect/SchemaGetter";
|
|
55
55
|
import * as Config from "effect/Config";
|
|
56
56
|
import * as NodeReadline from "node:readline";
|
|
57
|
+
import * as Scope from "effect/Scope";
|
|
58
|
+
import * as ChildProcess$1 from "effect/unstable/process/ChildProcess";
|
|
59
|
+
import * as ChildProcessSpawner$1 from "effect/unstable/process/ChildProcessSpawner";
|
|
57
60
|
import * as HttpApiClient from "effect/unstable/httpapi/HttpApiClient";
|
|
58
61
|
import * as Clock from "effect/Clock";
|
|
59
62
|
import * as Deferred from "effect/Deferred";
|
|
@@ -63,9 +66,6 @@ import { cast, dual } from "effect/Function";
|
|
|
63
66
|
import Mime from "@effect/platform-node/Mime";
|
|
64
67
|
import * as Arr from "effect/Array";
|
|
65
68
|
import * as Cache from "effect/Cache";
|
|
66
|
-
import * as Scope from "effect/Scope";
|
|
67
|
-
import * as ChildProcess$1 from "effect/unstable/process/ChildProcess";
|
|
68
|
-
import * as ChildProcessSpawner$1 from "effect/unstable/process/ChildProcessSpawner";
|
|
69
69
|
import * as Semaphore from "effect/Semaphore";
|
|
70
70
|
import * as Equal from "effect/Equal";
|
|
71
71
|
import * as HttpClient$1 from "effect/unstable/http/HttpClient";
|
|
@@ -232,7 +232,7 @@ const make$67 = () => {
|
|
|
232
232
|
const layer$62 = Layer.sync(NetService, make$67);
|
|
233
233
|
//#endregion
|
|
234
234
|
//#region package.json
|
|
235
|
-
var version = "0.0.
|
|
235
|
+
var version = "0.0.38";
|
|
236
236
|
//#endregion
|
|
237
237
|
//#region src/config.ts
|
|
238
238
|
/**
|
|
@@ -916,6 +916,11 @@ const resolveLatestVersion = Effect.fn("cli.updateCheck.resolve")(function* (sta
|
|
|
916
916
|
}).pipe(Effect.flatMap((encoded) => fs.writeFileString(cachePath, encoded)), Effect.ignore);
|
|
917
917
|
return latestVersion;
|
|
918
918
|
});
|
|
919
|
+
/**
|
|
920
|
+
* Both commands, because `npm i -g` only replaces the binary on disk. The
|
|
921
|
+
* service is a running process still executing the old code until
|
|
922
|
+
* `service update` restarts it onto the new one.
|
|
923
|
+
*/
|
|
919
924
|
function formatUpdateReminder(input) {
|
|
920
925
|
return [`Update available: ${PACKAGE_NAME}@${input.latestVersion} (running ${input.currentVersion}).`, ` npm i -g ${PACKAGE_NAME}@latest && p4 service update`].join("\n");
|
|
921
926
|
}
|
|
@@ -10835,7 +10840,7 @@ Layer.effectDiscard(runMigrations());
|
|
|
10835
10840
|
//#region src/persistence/Layers/Sqlite.ts
|
|
10836
10841
|
const defaultSqliteClientLoaders = {
|
|
10837
10842
|
bun: () => import("@effect/sql-sqlite-bun/SqliteClient"),
|
|
10838
|
-
node: () => import("./NodeSqliteClient-
|
|
10843
|
+
node: () => import("./NodeSqliteClient-Dq296EBh.mjs")
|
|
10839
10844
|
};
|
|
10840
10845
|
const makeRuntimeSqliteLayer = Effect.fn("makeRuntimeSqliteLayer")(function* (config) {
|
|
10841
10846
|
const runtime = process.versions.bun !== void 0 ? "bun" : "node";
|
|
@@ -12030,6 +12035,420 @@ const sessionCommand = Command.make("session").pipe(Command.withDescription("Man
|
|
|
12030
12035
|
]));
|
|
12031
12036
|
const authCommand = Command.make("auth").pipe(Command.withDescription("Manage the local auth control plane for headless deployments."), Command.withSubcommands([pairingCommand, sessionCommand]));
|
|
12032
12037
|
//#endregion
|
|
12038
|
+
//#region src/stream/collectUint8StreamText.ts
|
|
12039
|
+
const collectUint8StreamText = (input) => {
|
|
12040
|
+
const maxBytes = input.maxBytes ?? Number.POSITIVE_INFINITY;
|
|
12041
|
+
const truncatedMarker = input.truncatedMarker ?? "";
|
|
12042
|
+
return input.stream.pipe(Stream.runFold(() => ({
|
|
12043
|
+
chunks: [],
|
|
12044
|
+
bytes: 0,
|
|
12045
|
+
truncated: false
|
|
12046
|
+
}), (state, chunk) => {
|
|
12047
|
+
if (state.truncated) return state;
|
|
12048
|
+
const remainingBytes = maxBytes - state.bytes;
|
|
12049
|
+
if (remainingBytes <= 0) return {
|
|
12050
|
+
...state,
|
|
12051
|
+
truncated: true
|
|
12052
|
+
};
|
|
12053
|
+
const nextChunk = chunk.byteLength > remainingBytes ? chunk.slice(0, remainingBytes) : chunk;
|
|
12054
|
+
state.chunks.push(nextChunk);
|
|
12055
|
+
const bytes = state.bytes + nextChunk.byteLength;
|
|
12056
|
+
const truncated = chunk.byteLength > remainingBytes;
|
|
12057
|
+
return {
|
|
12058
|
+
chunks: state.chunks,
|
|
12059
|
+
bytes,
|
|
12060
|
+
truncated
|
|
12061
|
+
};
|
|
12062
|
+
}), Effect.map((state) => {
|
|
12063
|
+
const text = Buffer.concat(state.chunks, state.bytes).toString("utf8");
|
|
12064
|
+
return {
|
|
12065
|
+
text: state.truncated && truncatedMarker.length > 0 ? `${text}${truncatedMarker}` : text,
|
|
12066
|
+
bytes: state.bytes,
|
|
12067
|
+
truncated: state.truncated
|
|
12068
|
+
};
|
|
12069
|
+
}));
|
|
12070
|
+
};
|
|
12071
|
+
//#endregion
|
|
12072
|
+
//#region src/processRunner.ts
|
|
12073
|
+
const ProcessInvocationFields = {
|
|
12074
|
+
command: Schema$1.String,
|
|
12075
|
+
argumentCount: Schema$1.Number,
|
|
12076
|
+
cwd: Schema$1.optional(Schema$1.String),
|
|
12077
|
+
spawnCwd: Schema$1.optional(Schema$1.String)
|
|
12078
|
+
};
|
|
12079
|
+
const formatProcessInvocation = (input) => {
|
|
12080
|
+
const executionCwd = input.spawnCwd ?? input.cwd;
|
|
12081
|
+
return executionCwd === void 0 ? `'${input.command}'` : `'${input.command}' in '${executionCwd}'`;
|
|
12082
|
+
};
|
|
12083
|
+
var ProcessSpawnError = class extends Schema$1.TaggedErrorClass()("ProcessSpawnError", {
|
|
12084
|
+
...ProcessInvocationFields,
|
|
12085
|
+
resolvedCommand: Schema$1.optional(Schema$1.String),
|
|
12086
|
+
resolvedArgumentCount: Schema$1.optional(Schema$1.Number),
|
|
12087
|
+
shell: Schema$1.optional(Schema$1.Boolean),
|
|
12088
|
+
cause: Schema$1.Defect()
|
|
12089
|
+
}) {
|
|
12090
|
+
get message() {
|
|
12091
|
+
return `Failed to spawn process ${formatProcessInvocation(this)}`;
|
|
12092
|
+
}
|
|
12093
|
+
};
|
|
12094
|
+
var ProcessStdinError = class extends Schema$1.TaggedErrorClass()("ProcessStdinError", {
|
|
12095
|
+
...ProcessInvocationFields,
|
|
12096
|
+
stdinBytes: Schema$1.Number,
|
|
12097
|
+
cause: Schema$1.Defect()
|
|
12098
|
+
}) {
|
|
12099
|
+
get message() {
|
|
12100
|
+
return `Failed to write stdin for process ${formatProcessInvocation(this)}`;
|
|
12101
|
+
}
|
|
12102
|
+
};
|
|
12103
|
+
var ProcessOutputLimitError = class extends Schema$1.TaggedErrorClass()("ProcessOutputLimitError", {
|
|
12104
|
+
...ProcessInvocationFields,
|
|
12105
|
+
stream: Schema$1.Literals(["stdout", "stderr"]),
|
|
12106
|
+
maxBytes: Schema$1.Number,
|
|
12107
|
+
observedBytes: Schema$1.Number
|
|
12108
|
+
}) {
|
|
12109
|
+
get message() {
|
|
12110
|
+
return `Process ${formatProcessInvocation(this)} ${this.stream} produced ${this.observedBytes} bytes, exceeding the ${this.maxBytes} byte limit`;
|
|
12111
|
+
}
|
|
12112
|
+
};
|
|
12113
|
+
var ProcessReadError = class extends Schema$1.TaggedErrorClass()("ProcessReadError", {
|
|
12114
|
+
...ProcessInvocationFields,
|
|
12115
|
+
stream: Schema$1.Literals([
|
|
12116
|
+
"stdout",
|
|
12117
|
+
"stderr",
|
|
12118
|
+
"exitCode"
|
|
12119
|
+
]),
|
|
12120
|
+
cause: Schema$1.Defect()
|
|
12121
|
+
}) {
|
|
12122
|
+
get message() {
|
|
12123
|
+
return `Failed to read ${this.stream} for process ${formatProcessInvocation(this)}`;
|
|
12124
|
+
}
|
|
12125
|
+
};
|
|
12126
|
+
var ProcessTimeoutError = class extends Schema$1.TaggedErrorClass()("ProcessTimeoutError", {
|
|
12127
|
+
...ProcessInvocationFields,
|
|
12128
|
+
timeoutMs: Schema$1.Number
|
|
12129
|
+
}) {
|
|
12130
|
+
get message() {
|
|
12131
|
+
return `Process ${formatProcessInvocation(this)} timed out after ${this.timeoutMs}ms`;
|
|
12132
|
+
}
|
|
12133
|
+
};
|
|
12134
|
+
Schema$1.Union([
|
|
12135
|
+
ProcessSpawnError,
|
|
12136
|
+
ProcessStdinError,
|
|
12137
|
+
ProcessOutputLimitError,
|
|
12138
|
+
ProcessReadError,
|
|
12139
|
+
ProcessTimeoutError
|
|
12140
|
+
]);
|
|
12141
|
+
var ProcessRunner = class extends Context.Service()("@p4code/cli/processRunner") {};
|
|
12142
|
+
const DEFAULT_TIMEOUT = "60 seconds";
|
|
12143
|
+
const DEFAULT_MAX_OUTPUT_BYTES$2 = 8 * 1024 * 1024;
|
|
12144
|
+
const WINDOWS_COMMAND_NOT_FOUND_PATTERNS = [
|
|
12145
|
+
/is not recognized as an internal or external command/i,
|
|
12146
|
+
/n.o . reconhecido como um comando interno/i,
|
|
12147
|
+
/non . riconosciuto come comando interno o esterno/i,
|
|
12148
|
+
/n.est pas reconnu en tant que commande interne/i,
|
|
12149
|
+
/no se reconoce como un comando interno o externo/i,
|
|
12150
|
+
/wird nicht als interner oder externer befehl/i
|
|
12151
|
+
];
|
|
12152
|
+
function hasWindowsCommandNotFoundMessage(output) {
|
|
12153
|
+
return WINDOWS_COMMAND_NOT_FOUND_PATTERNS.some((pattern) => pattern.test(output));
|
|
12154
|
+
}
|
|
12155
|
+
const isWindowsCommandNotFound = Effect.fn("processRunner.isWindowsCommandNotFound")(function* (code, stderr) {
|
|
12156
|
+
if ((yield* HostProcessPlatform) !== "win32") return false;
|
|
12157
|
+
if (code === 9009) return true;
|
|
12158
|
+
return hasWindowsCommandNotFoundMessage(stderr);
|
|
12159
|
+
});
|
|
12160
|
+
const collectText = Effect.fn("processRunner.collectText")(function* (input) {
|
|
12161
|
+
const stream = input.stream.pipe(Stream.mapError((cause) => new ProcessReadError({
|
|
12162
|
+
command: input.command,
|
|
12163
|
+
argumentCount: input.args.length,
|
|
12164
|
+
cwd: input.cwd,
|
|
12165
|
+
spawnCwd: input.spawnCwd,
|
|
12166
|
+
stream: input.streamName,
|
|
12167
|
+
cause
|
|
12168
|
+
})));
|
|
12169
|
+
if (input.outputMode === "truncate") return yield* collectUint8StreamText({
|
|
12170
|
+
stream,
|
|
12171
|
+
maxBytes: input.maxOutputBytes,
|
|
12172
|
+
truncatedMarker: input.truncatedMarker
|
|
12173
|
+
});
|
|
12174
|
+
return yield* stream.pipe(Stream.runFoldEffect(() => ({
|
|
12175
|
+
chunks: [],
|
|
12176
|
+
bytes: 0
|
|
12177
|
+
}), (state, chunk) => {
|
|
12178
|
+
const remainingBytes = input.maxOutputBytes - state.bytes;
|
|
12179
|
+
if (chunk.byteLength > remainingBytes) return Effect.fail(new ProcessOutputLimitError({
|
|
12180
|
+
command: input.command,
|
|
12181
|
+
argumentCount: input.args.length,
|
|
12182
|
+
cwd: input.cwd,
|
|
12183
|
+
spawnCwd: input.spawnCwd,
|
|
12184
|
+
stream: input.streamName,
|
|
12185
|
+
maxBytes: input.maxOutputBytes,
|
|
12186
|
+
observedBytes: state.bytes + chunk.byteLength
|
|
12187
|
+
}));
|
|
12188
|
+
state.chunks.push(chunk);
|
|
12189
|
+
return Effect.succeed({
|
|
12190
|
+
chunks: state.chunks,
|
|
12191
|
+
bytes: state.bytes + chunk.byteLength
|
|
12192
|
+
});
|
|
12193
|
+
}), Effect.map((state) => ({
|
|
12194
|
+
text: Buffer.concat(state.chunks, state.bytes).toString("utf8"),
|
|
12195
|
+
bytes: state.bytes,
|
|
12196
|
+
truncated: false
|
|
12197
|
+
})));
|
|
12198
|
+
});
|
|
12199
|
+
function finalizeRunProcess(effect, input) {
|
|
12200
|
+
const timeout = Duration.fromInputUnsafe(input.timeout ?? DEFAULT_TIMEOUT);
|
|
12201
|
+
const timeoutBehavior = input.timeoutBehavior ?? "error";
|
|
12202
|
+
return effect.pipe(Effect.scoped, Effect.timeoutOption(timeout), Effect.flatMap((result) => {
|
|
12203
|
+
if (Option.isSome(result)) return Effect.succeed(result.value);
|
|
12204
|
+
if (timeoutBehavior === "timedOutResult") return Effect.succeed({
|
|
12205
|
+
stdout: "",
|
|
12206
|
+
stderr: "",
|
|
12207
|
+
code: null,
|
|
12208
|
+
timedOut: true,
|
|
12209
|
+
stdoutTruncated: false,
|
|
12210
|
+
stderrTruncated: false
|
|
12211
|
+
});
|
|
12212
|
+
return Effect.fail(new ProcessTimeoutError({
|
|
12213
|
+
command: input.command,
|
|
12214
|
+
argumentCount: input.args.length,
|
|
12215
|
+
cwd: input.cwd,
|
|
12216
|
+
spawnCwd: input.spawnCwd,
|
|
12217
|
+
timeoutMs: Duration.toMillis(timeout)
|
|
12218
|
+
}));
|
|
12219
|
+
}));
|
|
12220
|
+
}
|
|
12221
|
+
const runProcessCore = Effect.fn("processRunner.runProcessCore")(function* (spawner, input) {
|
|
12222
|
+
const maxOutputBytes = input.maxOutputBytes ?? DEFAULT_MAX_OUTPUT_BYTES$2;
|
|
12223
|
+
const outputMode = input.outputMode ?? "error";
|
|
12224
|
+
const truncatedMarker = input.truncatedMarker ?? "";
|
|
12225
|
+
const extendEnv = input.env !== void 0;
|
|
12226
|
+
const spawnCommand = yield* resolveSpawnCommand(input.command, input.args, input.env === void 0 ? {} : {
|
|
12227
|
+
env: input.env,
|
|
12228
|
+
extendEnv
|
|
12229
|
+
});
|
|
12230
|
+
const child = yield* spawner.spawn(ChildProcess$1.make(spawnCommand.command, spawnCommand.args, {
|
|
12231
|
+
...input.spawnCwd ?? input.cwd ? { cwd: input.spawnCwd ?? input.cwd } : {},
|
|
12232
|
+
...input.env !== void 0 ? {
|
|
12233
|
+
env: input.env,
|
|
12234
|
+
extendEnv
|
|
12235
|
+
} : {},
|
|
12236
|
+
shell: spawnCommand.shell
|
|
12237
|
+
})).pipe(Effect.mapError((cause) => new ProcessSpawnError({
|
|
12238
|
+
command: input.command,
|
|
12239
|
+
argumentCount: input.args.length,
|
|
12240
|
+
cwd: input.cwd,
|
|
12241
|
+
spawnCwd: input.spawnCwd,
|
|
12242
|
+
resolvedCommand: spawnCommand.command,
|
|
12243
|
+
resolvedArgumentCount: spawnCommand.args.length,
|
|
12244
|
+
shell: spawnCommand.shell,
|
|
12245
|
+
cause
|
|
12246
|
+
})));
|
|
12247
|
+
const stdin = input.stdin;
|
|
12248
|
+
const writeStdin = stdin === void 0 ? Effect.void : Stream.run(Stream.encodeText(Stream.make(stdin)), child.stdin).pipe(Effect.mapError((cause) => new ProcessStdinError({
|
|
12249
|
+
command: input.command,
|
|
12250
|
+
argumentCount: input.args.length,
|
|
12251
|
+
cwd: input.cwd,
|
|
12252
|
+
spawnCwd: input.spawnCwd,
|
|
12253
|
+
stdinBytes: Buffer.byteLength(stdin),
|
|
12254
|
+
cause
|
|
12255
|
+
})));
|
|
12256
|
+
const [stdout, stderr] = yield* Effect.all([
|
|
12257
|
+
collectText({
|
|
12258
|
+
command: input.command,
|
|
12259
|
+
args: input.args,
|
|
12260
|
+
cwd: input.cwd,
|
|
12261
|
+
spawnCwd: input.spawnCwd,
|
|
12262
|
+
streamName: "stdout",
|
|
12263
|
+
stream: child.stdout,
|
|
12264
|
+
maxOutputBytes,
|
|
12265
|
+
outputMode,
|
|
12266
|
+
truncatedMarker
|
|
12267
|
+
}),
|
|
12268
|
+
collectText({
|
|
12269
|
+
command: input.command,
|
|
12270
|
+
args: input.args,
|
|
12271
|
+
cwd: input.cwd,
|
|
12272
|
+
spawnCwd: input.spawnCwd,
|
|
12273
|
+
streamName: "stderr",
|
|
12274
|
+
stream: child.stderr,
|
|
12275
|
+
maxOutputBytes,
|
|
12276
|
+
outputMode,
|
|
12277
|
+
truncatedMarker
|
|
12278
|
+
}),
|
|
12279
|
+
writeStdin
|
|
12280
|
+
], { concurrency: "unbounded" });
|
|
12281
|
+
const exitCode = yield* child.exitCode.pipe(Effect.mapError((cause) => new ProcessReadError({
|
|
12282
|
+
command: input.command,
|
|
12283
|
+
argumentCount: input.args.length,
|
|
12284
|
+
cwd: input.cwd,
|
|
12285
|
+
spawnCwd: input.spawnCwd,
|
|
12286
|
+
stream: "exitCode",
|
|
12287
|
+
cause
|
|
12288
|
+
})));
|
|
12289
|
+
return {
|
|
12290
|
+
stdout: stdout.text,
|
|
12291
|
+
stderr: stderr.text,
|
|
12292
|
+
code: exitCode,
|
|
12293
|
+
timedOut: false,
|
|
12294
|
+
stdoutTruncated: stdout.truncated,
|
|
12295
|
+
stderrTruncated: stderr.truncated
|
|
12296
|
+
};
|
|
12297
|
+
});
|
|
12298
|
+
const make$58 = Effect.fn("ProcessRunner.make")(function* () {
|
|
12299
|
+
const spawner = yield* ChildProcessSpawner$1.ChildProcessSpawner;
|
|
12300
|
+
const run = (input) => finalizeRunProcess(runProcessCore(spawner, input), input);
|
|
12301
|
+
return ProcessRunner.of({ run });
|
|
12302
|
+
});
|
|
12303
|
+
const layer$53 = Layer.effect(ProcessRunner, make$58());
|
|
12304
|
+
//#endregion
|
|
12305
|
+
//#region src/cli/completions.ts
|
|
12306
|
+
/**
|
|
12307
|
+
* Installs the zsh completion script for the `p4` command.
|
|
12308
|
+
*
|
|
12309
|
+
* zsh ships `_perforce` with `#compdef p4 p4d`, so out of the box tabbing
|
|
12310
|
+
* through our subcommands prints "unhandled perforce command: …" from its
|
|
12311
|
+
* fallback message. `compinit` resolves duplicate `#compdef` claims by fpath
|
|
12312
|
+
* order — first match wins — and `_perforce` lives in the *last* fpath entry
|
|
12313
|
+
* (`/usr/share/zsh/<ver>/functions`). Writing our own `_p4` into any earlier
|
|
12314
|
+
* directory therefore replaces it outright, with no shell-config edit.
|
|
12315
|
+
*/
|
|
12316
|
+
const COMPLETION_FILE = "_p4";
|
|
12317
|
+
const GENERATE_TIMEOUT_MS = 1e4;
|
|
12318
|
+
/**
|
|
12319
|
+
* Directories ahead of `/usr/share/zsh/*` in a default fpath, most specific
|
|
12320
|
+
* first. `~/.zfunc` is the fallback and is the only one needing an fpath line.
|
|
12321
|
+
*/
|
|
12322
|
+
function candidateDirectories(input) {
|
|
12323
|
+
return [
|
|
12324
|
+
{
|
|
12325
|
+
dir: input.join(input.homeDir, ".oh-my-zsh", "custom", "completions"),
|
|
12326
|
+
requiresFpathEntry: false
|
|
12327
|
+
},
|
|
12328
|
+
{
|
|
12329
|
+
dir: "/opt/homebrew/share/zsh/site-functions",
|
|
12330
|
+
requiresFpathEntry: false
|
|
12331
|
+
},
|
|
12332
|
+
{
|
|
12333
|
+
dir: "/usr/local/share/zsh/site-functions",
|
|
12334
|
+
requiresFpathEntry: false
|
|
12335
|
+
},
|
|
12336
|
+
{
|
|
12337
|
+
dir: input.join(input.homeDir, ".zfunc"),
|
|
12338
|
+
requiresFpathEntry: true
|
|
12339
|
+
}
|
|
12340
|
+
];
|
|
12341
|
+
}
|
|
12342
|
+
/**
|
|
12343
|
+
* Asks the running CLI for its own completion script rather than rendering one
|
|
12344
|
+
* here, so the output cannot drift from what `--completions` produces.
|
|
12345
|
+
*/
|
|
12346
|
+
const generateZshCompletionScript = Effect.fn("cli.completions.generate")(function* () {
|
|
12347
|
+
const runner = yield* ProcessRunner;
|
|
12348
|
+
const execPath = yield* HostProcessExecutablePath;
|
|
12349
|
+
const entryPath = (yield* HostProcessArguments)[1] ?? "";
|
|
12350
|
+
if (entryPath === "") return null;
|
|
12351
|
+
const result = yield* runner.run({
|
|
12352
|
+
command: execPath,
|
|
12353
|
+
args: [
|
|
12354
|
+
entryPath,
|
|
12355
|
+
"--completions",
|
|
12356
|
+
"zsh"
|
|
12357
|
+
],
|
|
12358
|
+
timeout: GENERATE_TIMEOUT_MS
|
|
12359
|
+
});
|
|
12360
|
+
if (result.code !== 0) return null;
|
|
12361
|
+
const script = result.stdout.trim();
|
|
12362
|
+
return script.length === 0 ? null : `${script}\n`;
|
|
12363
|
+
});
|
|
12364
|
+
/**
|
|
12365
|
+
* Best-effort: returns null when completions are not applicable (Windows, no
|
|
12366
|
+
* writable directory) rather than failing the command that asked for them.
|
|
12367
|
+
* Installing completions is never the reason an install should fail.
|
|
12368
|
+
*/
|
|
12369
|
+
const installZshCompletion = Effect.fn("cli.completions.install")(function* () {
|
|
12370
|
+
if ((yield* HostProcessPlatform) === "win32") return null;
|
|
12371
|
+
const fs = yield* FileSystem.FileSystem;
|
|
12372
|
+
const path = yield* Path.Path;
|
|
12373
|
+
const homeDir = (yield* HostProcessEnvironment).HOME ?? "";
|
|
12374
|
+
if (homeDir === "") return null;
|
|
12375
|
+
const script = yield* generateZshCompletionScript();
|
|
12376
|
+
if (script === null) return null;
|
|
12377
|
+
for (const candidate of candidateDirectories({
|
|
12378
|
+
homeDir,
|
|
12379
|
+
join: path.join
|
|
12380
|
+
})) {
|
|
12381
|
+
if (candidate.requiresFpathEntry) {
|
|
12382
|
+
if (!(yield* fs.makeDirectory(candidate.dir, { recursive: true }).pipe(Effect.as(true), Effect.orElseSucceed(() => false)))) continue;
|
|
12383
|
+
} else if (!(yield* fs.exists(candidate.dir).pipe(Effect.orElseSucceed(() => false)))) continue;
|
|
12384
|
+
const target = path.join(candidate.dir, COMPLETION_FILE);
|
|
12385
|
+
if (!(yield* fs.writeFileString(target, script).pipe(Effect.as(true), Effect.orElseSucceed(() => false)))) continue;
|
|
12386
|
+
return {
|
|
12387
|
+
path: target,
|
|
12388
|
+
requiresFpathEntry: candidate.requiresFpathEntry
|
|
12389
|
+
};
|
|
12390
|
+
}
|
|
12391
|
+
return null;
|
|
12392
|
+
});
|
|
12393
|
+
const HINT_MARKER_FILE = "completions-hint";
|
|
12394
|
+
const HINT_INTERVAL_MS = 1440 * 60 * 1e3;
|
|
12395
|
+
const COMPLETIONS_HINT = "Shell completions are not installed, so tab completion shows Perforce's. Run `p4 completions install`.";
|
|
12396
|
+
/**
|
|
12397
|
+
* A hint when no `_p4` is on the fpath, at most once a day.
|
|
12398
|
+
*
|
|
12399
|
+
* A stat of a few directories, deliberately not the install itself: generating
|
|
12400
|
+
* the script spawns a subprocess, which is far too much to do on every command
|
|
12401
|
+
* just in case. Throttled through a marker file so an unactioned hint does not
|
|
12402
|
+
* become noise on every invocation.
|
|
12403
|
+
*/
|
|
12404
|
+
const resolveMissingCompletionsHint = Effect.fn("cli.completions.hint")(function* (input) {
|
|
12405
|
+
if ((yield* HostProcessPlatform) === "win32") return null;
|
|
12406
|
+
const fs = yield* FileSystem.FileSystem;
|
|
12407
|
+
const path = yield* Path.Path;
|
|
12408
|
+
const homeDir = (yield* HostProcessEnvironment).HOME ?? "";
|
|
12409
|
+
if (homeDir === "") return null;
|
|
12410
|
+
for (const candidate of candidateDirectories({
|
|
12411
|
+
homeDir,
|
|
12412
|
+
join: path.join
|
|
12413
|
+
})) if (yield* fs.exists(path.join(candidate.dir, COMPLETION_FILE)).pipe(Effect.orElseSucceed(() => false))) return null;
|
|
12414
|
+
const markerPath = path.join(input.stateDir, HINT_MARKER_FILE);
|
|
12415
|
+
const marker = yield* fs.stat(markerPath).pipe(Effect.asSome, Effect.orElseSucceed(() => null));
|
|
12416
|
+
const nowMs = DateTime.toEpochMillis(yield* DateTime.now);
|
|
12417
|
+
if (marker !== null && Option.isSome(marker)) {
|
|
12418
|
+
const mtime = marker.value.mtime;
|
|
12419
|
+
if (Option.isSome(mtime) && nowMs - mtime.value.getTime() < HINT_INTERVAL_MS) return null;
|
|
12420
|
+
}
|
|
12421
|
+
yield* fs.makeDirectory(input.stateDir, { recursive: true }).pipe(Effect.ignore);
|
|
12422
|
+
yield* fs.writeFileString(markerPath, "").pipe(Effect.ignore);
|
|
12423
|
+
return COMPLETIONS_HINT;
|
|
12424
|
+
});
|
|
12425
|
+
/**
|
|
12426
|
+
* Standalone so completions never depend on the service. A laptop that only
|
|
12427
|
+
* runs clients has no service to install, and a host whose `service install`
|
|
12428
|
+
* fails still deserves working completions.
|
|
12429
|
+
*/
|
|
12430
|
+
const completionsCommand = Command.make("completions").pipe(Command.withDescription("Manage shell completions for the p4 command."), Command.withSubcommands([Command.make("install").pipe(Command.withDescription("Install zsh completions, replacing the Perforce completion that claims `p4`."), Command.withHandler(() => Effect.gen(function* () {
|
|
12431
|
+
const install = yield* installZshCompletion();
|
|
12432
|
+
if (install === null) {
|
|
12433
|
+
yield* Console.error([
|
|
12434
|
+
"Could not install completions automatically.",
|
|
12435
|
+
"Write them yourself with:",
|
|
12436
|
+
" p4 --completions zsh > <a directory on your fpath>/_p4"
|
|
12437
|
+
].join("\n"));
|
|
12438
|
+
return;
|
|
12439
|
+
}
|
|
12440
|
+
yield* Console.log(formatZshCompletionInstall(install));
|
|
12441
|
+
}).pipe(Effect.provide(layer$53))))]));
|
|
12442
|
+
function formatZshCompletionInstall(install) {
|
|
12443
|
+
const installed = `Installed zsh completions at ${install.path}.`;
|
|
12444
|
+
if (!install.requiresFpathEntry) return `${installed} Open a new shell to use them.`;
|
|
12445
|
+
return [
|
|
12446
|
+
installed,
|
|
12447
|
+
`Add this to ~/.zshrc before compinit runs, then open a new shell:`,
|
|
12448
|
+
` fpath=(${install.path.replace(/\/[^/]+$/u, "")} $fpath)`
|
|
12449
|
+
].join("\n");
|
|
12450
|
+
}
|
|
12451
|
+
//#endregion
|
|
12033
12452
|
//#region src/orchestration/Services/OrchestrationEngine.ts
|
|
12034
12453
|
/**
|
|
12035
12454
|
* OrchestrationEngineService - Service tag for orchestration engine access.
|
|
@@ -16543,273 +16962,6 @@ function mergeGitStatusParts(local, remote) {
|
|
|
16543
16962
|
};
|
|
16544
16963
|
}
|
|
16545
16964
|
//#endregion
|
|
16546
|
-
//#region src/stream/collectUint8StreamText.ts
|
|
16547
|
-
const collectUint8StreamText = (input) => {
|
|
16548
|
-
const maxBytes = input.maxBytes ?? Number.POSITIVE_INFINITY;
|
|
16549
|
-
const truncatedMarker = input.truncatedMarker ?? "";
|
|
16550
|
-
return input.stream.pipe(Stream.runFold(() => ({
|
|
16551
|
-
chunks: [],
|
|
16552
|
-
bytes: 0,
|
|
16553
|
-
truncated: false
|
|
16554
|
-
}), (state, chunk) => {
|
|
16555
|
-
if (state.truncated) return state;
|
|
16556
|
-
const remainingBytes = maxBytes - state.bytes;
|
|
16557
|
-
if (remainingBytes <= 0) return {
|
|
16558
|
-
...state,
|
|
16559
|
-
truncated: true
|
|
16560
|
-
};
|
|
16561
|
-
const nextChunk = chunk.byteLength > remainingBytes ? chunk.slice(0, remainingBytes) : chunk;
|
|
16562
|
-
state.chunks.push(nextChunk);
|
|
16563
|
-
const bytes = state.bytes + nextChunk.byteLength;
|
|
16564
|
-
const truncated = chunk.byteLength > remainingBytes;
|
|
16565
|
-
return {
|
|
16566
|
-
chunks: state.chunks,
|
|
16567
|
-
bytes,
|
|
16568
|
-
truncated
|
|
16569
|
-
};
|
|
16570
|
-
}), Effect.map((state) => {
|
|
16571
|
-
const text = Buffer.concat(state.chunks, state.bytes).toString("utf8");
|
|
16572
|
-
return {
|
|
16573
|
-
text: state.truncated && truncatedMarker.length > 0 ? `${text}${truncatedMarker}` : text,
|
|
16574
|
-
bytes: state.bytes,
|
|
16575
|
-
truncated: state.truncated
|
|
16576
|
-
};
|
|
16577
|
-
}));
|
|
16578
|
-
};
|
|
16579
|
-
//#endregion
|
|
16580
|
-
//#region src/processRunner.ts
|
|
16581
|
-
const ProcessInvocationFields = {
|
|
16582
|
-
command: Schema$1.String,
|
|
16583
|
-
argumentCount: Schema$1.Number,
|
|
16584
|
-
cwd: Schema$1.optional(Schema$1.String),
|
|
16585
|
-
spawnCwd: Schema$1.optional(Schema$1.String)
|
|
16586
|
-
};
|
|
16587
|
-
const formatProcessInvocation = (input) => {
|
|
16588
|
-
const executionCwd = input.spawnCwd ?? input.cwd;
|
|
16589
|
-
return executionCwd === void 0 ? `'${input.command}'` : `'${input.command}' in '${executionCwd}'`;
|
|
16590
|
-
};
|
|
16591
|
-
var ProcessSpawnError = class extends Schema$1.TaggedErrorClass()("ProcessSpawnError", {
|
|
16592
|
-
...ProcessInvocationFields,
|
|
16593
|
-
resolvedCommand: Schema$1.optional(Schema$1.String),
|
|
16594
|
-
resolvedArgumentCount: Schema$1.optional(Schema$1.Number),
|
|
16595
|
-
shell: Schema$1.optional(Schema$1.Boolean),
|
|
16596
|
-
cause: Schema$1.Defect()
|
|
16597
|
-
}) {
|
|
16598
|
-
get message() {
|
|
16599
|
-
return `Failed to spawn process ${formatProcessInvocation(this)}`;
|
|
16600
|
-
}
|
|
16601
|
-
};
|
|
16602
|
-
var ProcessStdinError = class extends Schema$1.TaggedErrorClass()("ProcessStdinError", {
|
|
16603
|
-
...ProcessInvocationFields,
|
|
16604
|
-
stdinBytes: Schema$1.Number,
|
|
16605
|
-
cause: Schema$1.Defect()
|
|
16606
|
-
}) {
|
|
16607
|
-
get message() {
|
|
16608
|
-
return `Failed to write stdin for process ${formatProcessInvocation(this)}`;
|
|
16609
|
-
}
|
|
16610
|
-
};
|
|
16611
|
-
var ProcessOutputLimitError = class extends Schema$1.TaggedErrorClass()("ProcessOutputLimitError", {
|
|
16612
|
-
...ProcessInvocationFields,
|
|
16613
|
-
stream: Schema$1.Literals(["stdout", "stderr"]),
|
|
16614
|
-
maxBytes: Schema$1.Number,
|
|
16615
|
-
observedBytes: Schema$1.Number
|
|
16616
|
-
}) {
|
|
16617
|
-
get message() {
|
|
16618
|
-
return `Process ${formatProcessInvocation(this)} ${this.stream} produced ${this.observedBytes} bytes, exceeding the ${this.maxBytes} byte limit`;
|
|
16619
|
-
}
|
|
16620
|
-
};
|
|
16621
|
-
var ProcessReadError = class extends Schema$1.TaggedErrorClass()("ProcessReadError", {
|
|
16622
|
-
...ProcessInvocationFields,
|
|
16623
|
-
stream: Schema$1.Literals([
|
|
16624
|
-
"stdout",
|
|
16625
|
-
"stderr",
|
|
16626
|
-
"exitCode"
|
|
16627
|
-
]),
|
|
16628
|
-
cause: Schema$1.Defect()
|
|
16629
|
-
}) {
|
|
16630
|
-
get message() {
|
|
16631
|
-
return `Failed to read ${this.stream} for process ${formatProcessInvocation(this)}`;
|
|
16632
|
-
}
|
|
16633
|
-
};
|
|
16634
|
-
var ProcessTimeoutError = class extends Schema$1.TaggedErrorClass()("ProcessTimeoutError", {
|
|
16635
|
-
...ProcessInvocationFields,
|
|
16636
|
-
timeoutMs: Schema$1.Number
|
|
16637
|
-
}) {
|
|
16638
|
-
get message() {
|
|
16639
|
-
return `Process ${formatProcessInvocation(this)} timed out after ${this.timeoutMs}ms`;
|
|
16640
|
-
}
|
|
16641
|
-
};
|
|
16642
|
-
Schema$1.Union([
|
|
16643
|
-
ProcessSpawnError,
|
|
16644
|
-
ProcessStdinError,
|
|
16645
|
-
ProcessOutputLimitError,
|
|
16646
|
-
ProcessReadError,
|
|
16647
|
-
ProcessTimeoutError
|
|
16648
|
-
]);
|
|
16649
|
-
var ProcessRunner = class extends Context.Service()("@p4code/cli/processRunner") {};
|
|
16650
|
-
const DEFAULT_TIMEOUT = "60 seconds";
|
|
16651
|
-
const DEFAULT_MAX_OUTPUT_BYTES$2 = 8 * 1024 * 1024;
|
|
16652
|
-
const WINDOWS_COMMAND_NOT_FOUND_PATTERNS = [
|
|
16653
|
-
/is not recognized as an internal or external command/i,
|
|
16654
|
-
/n.o . reconhecido como um comando interno/i,
|
|
16655
|
-
/non . riconosciuto come comando interno o esterno/i,
|
|
16656
|
-
/n.est pas reconnu en tant que commande interne/i,
|
|
16657
|
-
/no se reconoce como un comando interno o externo/i,
|
|
16658
|
-
/wird nicht als interner oder externer befehl/i
|
|
16659
|
-
];
|
|
16660
|
-
function hasWindowsCommandNotFoundMessage(output) {
|
|
16661
|
-
return WINDOWS_COMMAND_NOT_FOUND_PATTERNS.some((pattern) => pattern.test(output));
|
|
16662
|
-
}
|
|
16663
|
-
const isWindowsCommandNotFound = Effect.fn("processRunner.isWindowsCommandNotFound")(function* (code, stderr) {
|
|
16664
|
-
if ((yield* HostProcessPlatform) !== "win32") return false;
|
|
16665
|
-
if (code === 9009) return true;
|
|
16666
|
-
return hasWindowsCommandNotFoundMessage(stderr);
|
|
16667
|
-
});
|
|
16668
|
-
const collectText = Effect.fn("processRunner.collectText")(function* (input) {
|
|
16669
|
-
const stream = input.stream.pipe(Stream.mapError((cause) => new ProcessReadError({
|
|
16670
|
-
command: input.command,
|
|
16671
|
-
argumentCount: input.args.length,
|
|
16672
|
-
cwd: input.cwd,
|
|
16673
|
-
spawnCwd: input.spawnCwd,
|
|
16674
|
-
stream: input.streamName,
|
|
16675
|
-
cause
|
|
16676
|
-
})));
|
|
16677
|
-
if (input.outputMode === "truncate") return yield* collectUint8StreamText({
|
|
16678
|
-
stream,
|
|
16679
|
-
maxBytes: input.maxOutputBytes,
|
|
16680
|
-
truncatedMarker: input.truncatedMarker
|
|
16681
|
-
});
|
|
16682
|
-
return yield* stream.pipe(Stream.runFoldEffect(() => ({
|
|
16683
|
-
chunks: [],
|
|
16684
|
-
bytes: 0
|
|
16685
|
-
}), (state, chunk) => {
|
|
16686
|
-
const remainingBytes = input.maxOutputBytes - state.bytes;
|
|
16687
|
-
if (chunk.byteLength > remainingBytes) return Effect.fail(new ProcessOutputLimitError({
|
|
16688
|
-
command: input.command,
|
|
16689
|
-
argumentCount: input.args.length,
|
|
16690
|
-
cwd: input.cwd,
|
|
16691
|
-
spawnCwd: input.spawnCwd,
|
|
16692
|
-
stream: input.streamName,
|
|
16693
|
-
maxBytes: input.maxOutputBytes,
|
|
16694
|
-
observedBytes: state.bytes + chunk.byteLength
|
|
16695
|
-
}));
|
|
16696
|
-
state.chunks.push(chunk);
|
|
16697
|
-
return Effect.succeed({
|
|
16698
|
-
chunks: state.chunks,
|
|
16699
|
-
bytes: state.bytes + chunk.byteLength
|
|
16700
|
-
});
|
|
16701
|
-
}), Effect.map((state) => ({
|
|
16702
|
-
text: Buffer.concat(state.chunks, state.bytes).toString("utf8"),
|
|
16703
|
-
bytes: state.bytes,
|
|
16704
|
-
truncated: false
|
|
16705
|
-
})));
|
|
16706
|
-
});
|
|
16707
|
-
function finalizeRunProcess(effect, input) {
|
|
16708
|
-
const timeout = Duration.fromInputUnsafe(input.timeout ?? DEFAULT_TIMEOUT);
|
|
16709
|
-
const timeoutBehavior = input.timeoutBehavior ?? "error";
|
|
16710
|
-
return effect.pipe(Effect.scoped, Effect.timeoutOption(timeout), Effect.flatMap((result) => {
|
|
16711
|
-
if (Option.isSome(result)) return Effect.succeed(result.value);
|
|
16712
|
-
if (timeoutBehavior === "timedOutResult") return Effect.succeed({
|
|
16713
|
-
stdout: "",
|
|
16714
|
-
stderr: "",
|
|
16715
|
-
code: null,
|
|
16716
|
-
timedOut: true,
|
|
16717
|
-
stdoutTruncated: false,
|
|
16718
|
-
stderrTruncated: false
|
|
16719
|
-
});
|
|
16720
|
-
return Effect.fail(new ProcessTimeoutError({
|
|
16721
|
-
command: input.command,
|
|
16722
|
-
argumentCount: input.args.length,
|
|
16723
|
-
cwd: input.cwd,
|
|
16724
|
-
spawnCwd: input.spawnCwd,
|
|
16725
|
-
timeoutMs: Duration.toMillis(timeout)
|
|
16726
|
-
}));
|
|
16727
|
-
}));
|
|
16728
|
-
}
|
|
16729
|
-
const runProcessCore = Effect.fn("processRunner.runProcessCore")(function* (spawner, input) {
|
|
16730
|
-
const maxOutputBytes = input.maxOutputBytes ?? DEFAULT_MAX_OUTPUT_BYTES$2;
|
|
16731
|
-
const outputMode = input.outputMode ?? "error";
|
|
16732
|
-
const truncatedMarker = input.truncatedMarker ?? "";
|
|
16733
|
-
const extendEnv = input.env !== void 0;
|
|
16734
|
-
const spawnCommand = yield* resolveSpawnCommand(input.command, input.args, input.env === void 0 ? {} : {
|
|
16735
|
-
env: input.env,
|
|
16736
|
-
extendEnv
|
|
16737
|
-
});
|
|
16738
|
-
const child = yield* spawner.spawn(ChildProcess$1.make(spawnCommand.command, spawnCommand.args, {
|
|
16739
|
-
...input.spawnCwd ?? input.cwd ? { cwd: input.spawnCwd ?? input.cwd } : {},
|
|
16740
|
-
...input.env !== void 0 ? {
|
|
16741
|
-
env: input.env,
|
|
16742
|
-
extendEnv
|
|
16743
|
-
} : {},
|
|
16744
|
-
shell: spawnCommand.shell
|
|
16745
|
-
})).pipe(Effect.mapError((cause) => new ProcessSpawnError({
|
|
16746
|
-
command: input.command,
|
|
16747
|
-
argumentCount: input.args.length,
|
|
16748
|
-
cwd: input.cwd,
|
|
16749
|
-
spawnCwd: input.spawnCwd,
|
|
16750
|
-
resolvedCommand: spawnCommand.command,
|
|
16751
|
-
resolvedArgumentCount: spawnCommand.args.length,
|
|
16752
|
-
shell: spawnCommand.shell,
|
|
16753
|
-
cause
|
|
16754
|
-
})));
|
|
16755
|
-
const stdin = input.stdin;
|
|
16756
|
-
const writeStdin = stdin === void 0 ? Effect.void : Stream.run(Stream.encodeText(Stream.make(stdin)), child.stdin).pipe(Effect.mapError((cause) => new ProcessStdinError({
|
|
16757
|
-
command: input.command,
|
|
16758
|
-
argumentCount: input.args.length,
|
|
16759
|
-
cwd: input.cwd,
|
|
16760
|
-
spawnCwd: input.spawnCwd,
|
|
16761
|
-
stdinBytes: Buffer.byteLength(stdin),
|
|
16762
|
-
cause
|
|
16763
|
-
})));
|
|
16764
|
-
const [stdout, stderr] = yield* Effect.all([
|
|
16765
|
-
collectText({
|
|
16766
|
-
command: input.command,
|
|
16767
|
-
args: input.args,
|
|
16768
|
-
cwd: input.cwd,
|
|
16769
|
-
spawnCwd: input.spawnCwd,
|
|
16770
|
-
streamName: "stdout",
|
|
16771
|
-
stream: child.stdout,
|
|
16772
|
-
maxOutputBytes,
|
|
16773
|
-
outputMode,
|
|
16774
|
-
truncatedMarker
|
|
16775
|
-
}),
|
|
16776
|
-
collectText({
|
|
16777
|
-
command: input.command,
|
|
16778
|
-
args: input.args,
|
|
16779
|
-
cwd: input.cwd,
|
|
16780
|
-
spawnCwd: input.spawnCwd,
|
|
16781
|
-
streamName: "stderr",
|
|
16782
|
-
stream: child.stderr,
|
|
16783
|
-
maxOutputBytes,
|
|
16784
|
-
outputMode,
|
|
16785
|
-
truncatedMarker
|
|
16786
|
-
}),
|
|
16787
|
-
writeStdin
|
|
16788
|
-
], { concurrency: "unbounded" });
|
|
16789
|
-
const exitCode = yield* child.exitCode.pipe(Effect.mapError((cause) => new ProcessReadError({
|
|
16790
|
-
command: input.command,
|
|
16791
|
-
argumentCount: input.args.length,
|
|
16792
|
-
cwd: input.cwd,
|
|
16793
|
-
spawnCwd: input.spawnCwd,
|
|
16794
|
-
stream: "exitCode",
|
|
16795
|
-
cause
|
|
16796
|
-
})));
|
|
16797
|
-
return {
|
|
16798
|
-
stdout: stdout.text,
|
|
16799
|
-
stderr: stderr.text,
|
|
16800
|
-
code: exitCode,
|
|
16801
|
-
timedOut: false,
|
|
16802
|
-
stdoutTruncated: stdout.truncated,
|
|
16803
|
-
stderrTruncated: stderr.truncated
|
|
16804
|
-
};
|
|
16805
|
-
});
|
|
16806
|
-
const make$58 = Effect.fn("ProcessRunner.make")(function* () {
|
|
16807
|
-
const spawner = yield* ChildProcessSpawner$1.ChildProcessSpawner;
|
|
16808
|
-
const run = (input) => finalizeRunProcess(runProcessCore(spawner, input), input);
|
|
16809
|
-
return ProcessRunner.of({ run });
|
|
16810
|
-
});
|
|
16811
|
-
const layer$53 = Layer.effect(ProcessRunner, make$58());
|
|
16812
|
-
//#endregion
|
|
16813
16965
|
//#region src/project/RepositoryIdentityResolver.ts
|
|
16814
16966
|
const DEFAULT_REPOSITORY_IDENTITY_CACHE_CAPACITY = 512;
|
|
16815
16967
|
const DEFAULT_POSITIVE_CACHE_TTL = Duration.minutes(1);
|
|
@@ -78243,104 +78395,6 @@ const serveCommand = Command.make("serve", { ...sharedServerCommandFlags }).pipe
|
|
|
78243
78395
|
forceAutoBootstrapProjectFromCwd: false
|
|
78244
78396
|
})));
|
|
78245
78397
|
//#endregion
|
|
78246
|
-
//#region src/cli/completions.ts
|
|
78247
|
-
/**
|
|
78248
|
-
* Installs the zsh completion script for the `p4` command.
|
|
78249
|
-
*
|
|
78250
|
-
* zsh ships `_perforce` with `#compdef p4 p4d`, so out of the box tabbing
|
|
78251
|
-
* through our subcommands prints "unhandled perforce command: …" from its
|
|
78252
|
-
* fallback message. `compinit` resolves duplicate `#compdef` claims by fpath
|
|
78253
|
-
* order — first match wins — and `_perforce` lives in the *last* fpath entry
|
|
78254
|
-
* (`/usr/share/zsh/<ver>/functions`). Writing our own `_p4` into any earlier
|
|
78255
|
-
* directory therefore replaces it outright, with no shell-config edit.
|
|
78256
|
-
*/
|
|
78257
|
-
const COMPLETION_FILE = "_p4";
|
|
78258
|
-
const GENERATE_TIMEOUT_MS = 1e4;
|
|
78259
|
-
/**
|
|
78260
|
-
* Directories ahead of `/usr/share/zsh/*` in a default fpath, most specific
|
|
78261
|
-
* first. `~/.zfunc` is the fallback and is the only one needing an fpath line.
|
|
78262
|
-
*/
|
|
78263
|
-
function candidateDirectories(input) {
|
|
78264
|
-
return [
|
|
78265
|
-
{
|
|
78266
|
-
dir: input.join(input.homeDir, ".oh-my-zsh", "custom", "completions"),
|
|
78267
|
-
requiresFpathEntry: false
|
|
78268
|
-
},
|
|
78269
|
-
{
|
|
78270
|
-
dir: "/opt/homebrew/share/zsh/site-functions",
|
|
78271
|
-
requiresFpathEntry: false
|
|
78272
|
-
},
|
|
78273
|
-
{
|
|
78274
|
-
dir: "/usr/local/share/zsh/site-functions",
|
|
78275
|
-
requiresFpathEntry: false
|
|
78276
|
-
},
|
|
78277
|
-
{
|
|
78278
|
-
dir: input.join(input.homeDir, ".zfunc"),
|
|
78279
|
-
requiresFpathEntry: true
|
|
78280
|
-
}
|
|
78281
|
-
];
|
|
78282
|
-
}
|
|
78283
|
-
/**
|
|
78284
|
-
* Asks the running CLI for its own completion script rather than rendering one
|
|
78285
|
-
* here, so the output cannot drift from what `--completions` produces.
|
|
78286
|
-
*/
|
|
78287
|
-
const generateZshCompletionScript = Effect.fn("cli.completions.generate")(function* () {
|
|
78288
|
-
const runner = yield* ProcessRunner;
|
|
78289
|
-
const execPath = yield* HostProcessExecutablePath;
|
|
78290
|
-
const entryPath = (yield* HostProcessArguments)[1] ?? "";
|
|
78291
|
-
if (entryPath === "") return null;
|
|
78292
|
-
const result = yield* runner.run({
|
|
78293
|
-
command: execPath,
|
|
78294
|
-
args: [
|
|
78295
|
-
entryPath,
|
|
78296
|
-
"--completions",
|
|
78297
|
-
"zsh"
|
|
78298
|
-
],
|
|
78299
|
-
timeout: GENERATE_TIMEOUT_MS
|
|
78300
|
-
});
|
|
78301
|
-
if (result.code !== 0) return null;
|
|
78302
|
-
const script = result.stdout.trim();
|
|
78303
|
-
return script.length === 0 ? null : `${script}\n`;
|
|
78304
|
-
});
|
|
78305
|
-
/**
|
|
78306
|
-
* Best-effort: returns null when completions are not applicable (Windows, no
|
|
78307
|
-
* writable directory) rather than failing the command that asked for them.
|
|
78308
|
-
* Installing completions is never the reason an install should fail.
|
|
78309
|
-
*/
|
|
78310
|
-
const installZshCompletion = Effect.fn("cli.completions.install")(function* () {
|
|
78311
|
-
if ((yield* HostProcessPlatform) === "win32") return null;
|
|
78312
|
-
const fs = yield* FileSystem.FileSystem;
|
|
78313
|
-
const path = yield* Path.Path;
|
|
78314
|
-
const homeDir = (yield* HostProcessEnvironment).HOME ?? "";
|
|
78315
|
-
if (homeDir === "") return null;
|
|
78316
|
-
const script = yield* generateZshCompletionScript();
|
|
78317
|
-
if (script === null) return null;
|
|
78318
|
-
for (const candidate of candidateDirectories({
|
|
78319
|
-
homeDir,
|
|
78320
|
-
join: path.join
|
|
78321
|
-
})) {
|
|
78322
|
-
if (candidate.requiresFpathEntry) {
|
|
78323
|
-
if (!(yield* fs.makeDirectory(candidate.dir, { recursive: true }).pipe(Effect.as(true), Effect.orElseSucceed(() => false)))) continue;
|
|
78324
|
-
} else if (!(yield* fs.exists(candidate.dir).pipe(Effect.orElseSucceed(() => false)))) continue;
|
|
78325
|
-
const target = path.join(candidate.dir, COMPLETION_FILE);
|
|
78326
|
-
if (!(yield* fs.writeFileString(target, script).pipe(Effect.as(true), Effect.orElseSucceed(() => false)))) continue;
|
|
78327
|
-
return {
|
|
78328
|
-
path: target,
|
|
78329
|
-
requiresFpathEntry: candidate.requiresFpathEntry
|
|
78330
|
-
};
|
|
78331
|
-
}
|
|
78332
|
-
return null;
|
|
78333
|
-
});
|
|
78334
|
-
function formatZshCompletionInstall(install) {
|
|
78335
|
-
const installed = `Installed zsh completions at ${install.path}.`;
|
|
78336
|
-
if (!install.requiresFpathEntry) return `${installed} Open a new shell to use them.`;
|
|
78337
|
-
return [
|
|
78338
|
-
installed,
|
|
78339
|
-
`Add this to ~/.zshrc before compinit runs, then open a new shell:`,
|
|
78340
|
-
` fpath=(${install.path.replace(/\/[^/]+$/u, "")} $fpath)`
|
|
78341
|
-
].join("\n");
|
|
78342
|
-
}
|
|
78343
|
-
//#endregion
|
|
78344
78398
|
//#region src/cli/service.ts
|
|
78345
78399
|
const isBootServiceCommandError = Schema$1.is(BootServiceCommandError);
|
|
78346
78400
|
const bootServiceLayer = (config) => layer$46({
|
|
@@ -78395,10 +78449,21 @@ const runServiceCommand = Effect.fn("cli.service.run")(function* (flags, run) {
|
|
|
78395
78449
|
if (baseDirNotice !== null) yield* Console.error(baseDirNotice);
|
|
78396
78450
|
return yield* run.pipe(Effect.tapError((error) => isBootServiceCommandError(error) ? BootService.pipe(Effect.flatMap((service) => Console.error(`Details: ${service.logPath}`))) : Effect.void), Effect.provide(bootServiceLayer(config)), Effect.provide(layer$53));
|
|
78397
78451
|
});
|
|
78452
|
+
/**
|
|
78453
|
+
* Runs on every `service install` and `service update`, including the
|
|
78454
|
+
* already-current paths. Gating it on "the unit changed" meant a host whose
|
|
78455
|
+
* service was already installed — the common case after the first setup — never
|
|
78456
|
+
* got completions at all.
|
|
78457
|
+
*/
|
|
78458
|
+
const reportZshCompletionInstall = Effect.gen(function* () {
|
|
78459
|
+
const completion = yield* installZshCompletion().pipe(Effect.orElseSucceed(() => null));
|
|
78460
|
+
if (completion !== null) yield* Console.log(formatZshCompletionInstall(completion));
|
|
78461
|
+
});
|
|
78398
78462
|
const serviceInstallCommand = Command.make("install", projectLocationFlags).pipe(Command.withDescription("Install P4Code as a background service for this user."), Command.withHandler((flags) => runServiceCommand(flags, Effect.gen(function* () {
|
|
78399
78463
|
const result = yield* reconcileService();
|
|
78400
78464
|
if (!result.changed) {
|
|
78401
78465
|
yield* Console.log(`P4Code service is already installed with @p4code/cli@${version}.`);
|
|
78466
|
+
yield* reportZshCompletionInstall;
|
|
78402
78467
|
return;
|
|
78403
78468
|
}
|
|
78404
78469
|
yield* Console.log(formatReconcileSuccess({
|
|
@@ -78407,14 +78472,14 @@ const serviceInstallCommand = Command.make("install", projectLocationFlags).pipe
|
|
|
78407
78472
|
cliVersion: version,
|
|
78408
78473
|
platform: yield* HostProcessPlatform
|
|
78409
78474
|
}));
|
|
78410
|
-
|
|
78411
|
-
if (completion !== null) yield* Console.log(formatZshCompletionInstall(completion));
|
|
78475
|
+
yield* reportZshCompletionInstall;
|
|
78412
78476
|
}))));
|
|
78413
78477
|
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* () {
|
|
78414
78478
|
const result = yield* reconcileService();
|
|
78415
78479
|
if (!result.changed) {
|
|
78416
78480
|
yield* (yield* BootService).restart;
|
|
78417
78481
|
yield* Console.log(`Restarted the P4Code service on @p4code/cli@${version}.`);
|
|
78482
|
+
yield* reportZshCompletionInstall;
|
|
78418
78483
|
return;
|
|
78419
78484
|
}
|
|
78420
78485
|
yield* Console.log(formatReconcileSuccess({
|
|
@@ -78423,6 +78488,7 @@ const serviceUpdateCommand = Command.make("update", projectLocationFlags).pipe(C
|
|
|
78423
78488
|
cliVersion: version,
|
|
78424
78489
|
platform: yield* HostProcessPlatform
|
|
78425
78490
|
}));
|
|
78491
|
+
yield* reportZshCompletionInstall;
|
|
78426
78492
|
}))));
|
|
78427
78493
|
const serviceUninstallCommand = Command.make("uninstall", projectLocationFlags).pipe(Command.withDescription("Stop and remove the P4Code background service."), Command.withHandler((flags) => runServiceCommand(flags, Effect.gen(function* () {
|
|
78428
78494
|
const removed = yield* (yield* BootService).uninstall;
|
|
@@ -78471,11 +78537,14 @@ const reportUpdateAvailable = Effect.gen(function* () {
|
|
|
78471
78537
|
stateDir: paths.stateDir
|
|
78472
78538
|
});
|
|
78473
78539
|
if (reminder !== null) yield* Console.error(reminder);
|
|
78540
|
+
const completionsHint = yield* resolveMissingCompletionsHint({ stateDir: paths.stateDir });
|
|
78541
|
+
if (completionsHint !== null) yield* Console.error(completionsHint);
|
|
78474
78542
|
}).pipe(Effect.ignore);
|
|
78475
78543
|
const makeCli = () => Command.make("p4", { ...sharedServerCommandFlags }).pipe(Command.withDescription("Run the P4Code server."), Command.withHandler((flags) => runServerCommand(flags)), Command.withSubcommands([
|
|
78476
78544
|
startCommand,
|
|
78477
78545
|
serveCommand,
|
|
78478
78546
|
authCommand,
|
|
78547
|
+
completionsCommand,
|
|
78479
78548
|
projectCommand,
|
|
78480
78549
|
serviceCommand
|
|
78481
78550
|
]));
|