@p4code/cli 0.0.37 → 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 +436 -372
- package/dist/client/assets/{DiffPanel-xkGxJH4k.js → DiffPanel-D0CF-iwb.js} +2 -2
- package/dist/client/assets/{FilePreviewPanel-CmJUrNYJ.js → FilePreviewPanel-BUGKMTB4.js} +2 -2
- package/dist/client/assets/{PreviewPanel-B5dlT-n2.js → PreviewPanel-Lk9ar7c8.js} +2 -2
- package/dist/client/assets/{fileCommentAnnotations-BKadMjLJ.js → fileCommentAnnotations-BNNcqDCJ.js} +2 -2
- package/dist/client/assets/{index-qr_OWE66.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
|
/**
|
|
@@ -10840,7 +10840,7 @@ Layer.effectDiscard(runMigrations());
|
|
|
10840
10840
|
//#region src/persistence/Layers/Sqlite.ts
|
|
10841
10841
|
const defaultSqliteClientLoaders = {
|
|
10842
10842
|
bun: () => import("@effect/sql-sqlite-bun/SqliteClient"),
|
|
10843
|
-
node: () => import("./NodeSqliteClient-
|
|
10843
|
+
node: () => import("./NodeSqliteClient-Dq296EBh.mjs")
|
|
10844
10844
|
};
|
|
10845
10845
|
const makeRuntimeSqliteLayer = Effect.fn("makeRuntimeSqliteLayer")(function* (config) {
|
|
10846
10846
|
const runtime = process.versions.bun !== void 0 ? "bun" : "node";
|
|
@@ -12035,6 +12035,420 @@ const sessionCommand = Command.make("session").pipe(Command.withDescription("Man
|
|
|
12035
12035
|
]));
|
|
12036
12036
|
const authCommand = Command.make("auth").pipe(Command.withDescription("Manage the local auth control plane for headless deployments."), Command.withSubcommands([pairingCommand, sessionCommand]));
|
|
12037
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
|
|
12038
12452
|
//#region src/orchestration/Services/OrchestrationEngine.ts
|
|
12039
12453
|
/**
|
|
12040
12454
|
* OrchestrationEngineService - Service tag for orchestration engine access.
|
|
@@ -16548,273 +16962,6 @@ function mergeGitStatusParts(local, remote) {
|
|
|
16548
16962
|
};
|
|
16549
16963
|
}
|
|
16550
16964
|
//#endregion
|
|
16551
|
-
//#region src/stream/collectUint8StreamText.ts
|
|
16552
|
-
const collectUint8StreamText = (input) => {
|
|
16553
|
-
const maxBytes = input.maxBytes ?? Number.POSITIVE_INFINITY;
|
|
16554
|
-
const truncatedMarker = input.truncatedMarker ?? "";
|
|
16555
|
-
return input.stream.pipe(Stream.runFold(() => ({
|
|
16556
|
-
chunks: [],
|
|
16557
|
-
bytes: 0,
|
|
16558
|
-
truncated: false
|
|
16559
|
-
}), (state, chunk) => {
|
|
16560
|
-
if (state.truncated) return state;
|
|
16561
|
-
const remainingBytes = maxBytes - state.bytes;
|
|
16562
|
-
if (remainingBytes <= 0) return {
|
|
16563
|
-
...state,
|
|
16564
|
-
truncated: true
|
|
16565
|
-
};
|
|
16566
|
-
const nextChunk = chunk.byteLength > remainingBytes ? chunk.slice(0, remainingBytes) : chunk;
|
|
16567
|
-
state.chunks.push(nextChunk);
|
|
16568
|
-
const bytes = state.bytes + nextChunk.byteLength;
|
|
16569
|
-
const truncated = chunk.byteLength > remainingBytes;
|
|
16570
|
-
return {
|
|
16571
|
-
chunks: state.chunks,
|
|
16572
|
-
bytes,
|
|
16573
|
-
truncated
|
|
16574
|
-
};
|
|
16575
|
-
}), Effect.map((state) => {
|
|
16576
|
-
const text = Buffer.concat(state.chunks, state.bytes).toString("utf8");
|
|
16577
|
-
return {
|
|
16578
|
-
text: state.truncated && truncatedMarker.length > 0 ? `${text}${truncatedMarker}` : text,
|
|
16579
|
-
bytes: state.bytes,
|
|
16580
|
-
truncated: state.truncated
|
|
16581
|
-
};
|
|
16582
|
-
}));
|
|
16583
|
-
};
|
|
16584
|
-
//#endregion
|
|
16585
|
-
//#region src/processRunner.ts
|
|
16586
|
-
const ProcessInvocationFields = {
|
|
16587
|
-
command: Schema$1.String,
|
|
16588
|
-
argumentCount: Schema$1.Number,
|
|
16589
|
-
cwd: Schema$1.optional(Schema$1.String),
|
|
16590
|
-
spawnCwd: Schema$1.optional(Schema$1.String)
|
|
16591
|
-
};
|
|
16592
|
-
const formatProcessInvocation = (input) => {
|
|
16593
|
-
const executionCwd = input.spawnCwd ?? input.cwd;
|
|
16594
|
-
return executionCwd === void 0 ? `'${input.command}'` : `'${input.command}' in '${executionCwd}'`;
|
|
16595
|
-
};
|
|
16596
|
-
var ProcessSpawnError = class extends Schema$1.TaggedErrorClass()("ProcessSpawnError", {
|
|
16597
|
-
...ProcessInvocationFields,
|
|
16598
|
-
resolvedCommand: Schema$1.optional(Schema$1.String),
|
|
16599
|
-
resolvedArgumentCount: Schema$1.optional(Schema$1.Number),
|
|
16600
|
-
shell: Schema$1.optional(Schema$1.Boolean),
|
|
16601
|
-
cause: Schema$1.Defect()
|
|
16602
|
-
}) {
|
|
16603
|
-
get message() {
|
|
16604
|
-
return `Failed to spawn process ${formatProcessInvocation(this)}`;
|
|
16605
|
-
}
|
|
16606
|
-
};
|
|
16607
|
-
var ProcessStdinError = class extends Schema$1.TaggedErrorClass()("ProcessStdinError", {
|
|
16608
|
-
...ProcessInvocationFields,
|
|
16609
|
-
stdinBytes: Schema$1.Number,
|
|
16610
|
-
cause: Schema$1.Defect()
|
|
16611
|
-
}) {
|
|
16612
|
-
get message() {
|
|
16613
|
-
return `Failed to write stdin for process ${formatProcessInvocation(this)}`;
|
|
16614
|
-
}
|
|
16615
|
-
};
|
|
16616
|
-
var ProcessOutputLimitError = class extends Schema$1.TaggedErrorClass()("ProcessOutputLimitError", {
|
|
16617
|
-
...ProcessInvocationFields,
|
|
16618
|
-
stream: Schema$1.Literals(["stdout", "stderr"]),
|
|
16619
|
-
maxBytes: Schema$1.Number,
|
|
16620
|
-
observedBytes: Schema$1.Number
|
|
16621
|
-
}) {
|
|
16622
|
-
get message() {
|
|
16623
|
-
return `Process ${formatProcessInvocation(this)} ${this.stream} produced ${this.observedBytes} bytes, exceeding the ${this.maxBytes} byte limit`;
|
|
16624
|
-
}
|
|
16625
|
-
};
|
|
16626
|
-
var ProcessReadError = class extends Schema$1.TaggedErrorClass()("ProcessReadError", {
|
|
16627
|
-
...ProcessInvocationFields,
|
|
16628
|
-
stream: Schema$1.Literals([
|
|
16629
|
-
"stdout",
|
|
16630
|
-
"stderr",
|
|
16631
|
-
"exitCode"
|
|
16632
|
-
]),
|
|
16633
|
-
cause: Schema$1.Defect()
|
|
16634
|
-
}) {
|
|
16635
|
-
get message() {
|
|
16636
|
-
return `Failed to read ${this.stream} for process ${formatProcessInvocation(this)}`;
|
|
16637
|
-
}
|
|
16638
|
-
};
|
|
16639
|
-
var ProcessTimeoutError = class extends Schema$1.TaggedErrorClass()("ProcessTimeoutError", {
|
|
16640
|
-
...ProcessInvocationFields,
|
|
16641
|
-
timeoutMs: Schema$1.Number
|
|
16642
|
-
}) {
|
|
16643
|
-
get message() {
|
|
16644
|
-
return `Process ${formatProcessInvocation(this)} timed out after ${this.timeoutMs}ms`;
|
|
16645
|
-
}
|
|
16646
|
-
};
|
|
16647
|
-
Schema$1.Union([
|
|
16648
|
-
ProcessSpawnError,
|
|
16649
|
-
ProcessStdinError,
|
|
16650
|
-
ProcessOutputLimitError,
|
|
16651
|
-
ProcessReadError,
|
|
16652
|
-
ProcessTimeoutError
|
|
16653
|
-
]);
|
|
16654
|
-
var ProcessRunner = class extends Context.Service()("@p4code/cli/processRunner") {};
|
|
16655
|
-
const DEFAULT_TIMEOUT = "60 seconds";
|
|
16656
|
-
const DEFAULT_MAX_OUTPUT_BYTES$2 = 8 * 1024 * 1024;
|
|
16657
|
-
const WINDOWS_COMMAND_NOT_FOUND_PATTERNS = [
|
|
16658
|
-
/is not recognized as an internal or external command/i,
|
|
16659
|
-
/n.o . reconhecido como um comando interno/i,
|
|
16660
|
-
/non . riconosciuto come comando interno o esterno/i,
|
|
16661
|
-
/n.est pas reconnu en tant que commande interne/i,
|
|
16662
|
-
/no se reconoce como un comando interno o externo/i,
|
|
16663
|
-
/wird nicht als interner oder externer befehl/i
|
|
16664
|
-
];
|
|
16665
|
-
function hasWindowsCommandNotFoundMessage(output) {
|
|
16666
|
-
return WINDOWS_COMMAND_NOT_FOUND_PATTERNS.some((pattern) => pattern.test(output));
|
|
16667
|
-
}
|
|
16668
|
-
const isWindowsCommandNotFound = Effect.fn("processRunner.isWindowsCommandNotFound")(function* (code, stderr) {
|
|
16669
|
-
if ((yield* HostProcessPlatform) !== "win32") return false;
|
|
16670
|
-
if (code === 9009) return true;
|
|
16671
|
-
return hasWindowsCommandNotFoundMessage(stderr);
|
|
16672
|
-
});
|
|
16673
|
-
const collectText = Effect.fn("processRunner.collectText")(function* (input) {
|
|
16674
|
-
const stream = input.stream.pipe(Stream.mapError((cause) => new ProcessReadError({
|
|
16675
|
-
command: input.command,
|
|
16676
|
-
argumentCount: input.args.length,
|
|
16677
|
-
cwd: input.cwd,
|
|
16678
|
-
spawnCwd: input.spawnCwd,
|
|
16679
|
-
stream: input.streamName,
|
|
16680
|
-
cause
|
|
16681
|
-
})));
|
|
16682
|
-
if (input.outputMode === "truncate") return yield* collectUint8StreamText({
|
|
16683
|
-
stream,
|
|
16684
|
-
maxBytes: input.maxOutputBytes,
|
|
16685
|
-
truncatedMarker: input.truncatedMarker
|
|
16686
|
-
});
|
|
16687
|
-
return yield* stream.pipe(Stream.runFoldEffect(() => ({
|
|
16688
|
-
chunks: [],
|
|
16689
|
-
bytes: 0
|
|
16690
|
-
}), (state, chunk) => {
|
|
16691
|
-
const remainingBytes = input.maxOutputBytes - state.bytes;
|
|
16692
|
-
if (chunk.byteLength > remainingBytes) return Effect.fail(new ProcessOutputLimitError({
|
|
16693
|
-
command: input.command,
|
|
16694
|
-
argumentCount: input.args.length,
|
|
16695
|
-
cwd: input.cwd,
|
|
16696
|
-
spawnCwd: input.spawnCwd,
|
|
16697
|
-
stream: input.streamName,
|
|
16698
|
-
maxBytes: input.maxOutputBytes,
|
|
16699
|
-
observedBytes: state.bytes + chunk.byteLength
|
|
16700
|
-
}));
|
|
16701
|
-
state.chunks.push(chunk);
|
|
16702
|
-
return Effect.succeed({
|
|
16703
|
-
chunks: state.chunks,
|
|
16704
|
-
bytes: state.bytes + chunk.byteLength
|
|
16705
|
-
});
|
|
16706
|
-
}), Effect.map((state) => ({
|
|
16707
|
-
text: Buffer.concat(state.chunks, state.bytes).toString("utf8"),
|
|
16708
|
-
bytes: state.bytes,
|
|
16709
|
-
truncated: false
|
|
16710
|
-
})));
|
|
16711
|
-
});
|
|
16712
|
-
function finalizeRunProcess(effect, input) {
|
|
16713
|
-
const timeout = Duration.fromInputUnsafe(input.timeout ?? DEFAULT_TIMEOUT);
|
|
16714
|
-
const timeoutBehavior = input.timeoutBehavior ?? "error";
|
|
16715
|
-
return effect.pipe(Effect.scoped, Effect.timeoutOption(timeout), Effect.flatMap((result) => {
|
|
16716
|
-
if (Option.isSome(result)) return Effect.succeed(result.value);
|
|
16717
|
-
if (timeoutBehavior === "timedOutResult") return Effect.succeed({
|
|
16718
|
-
stdout: "",
|
|
16719
|
-
stderr: "",
|
|
16720
|
-
code: null,
|
|
16721
|
-
timedOut: true,
|
|
16722
|
-
stdoutTruncated: false,
|
|
16723
|
-
stderrTruncated: false
|
|
16724
|
-
});
|
|
16725
|
-
return Effect.fail(new ProcessTimeoutError({
|
|
16726
|
-
command: input.command,
|
|
16727
|
-
argumentCount: input.args.length,
|
|
16728
|
-
cwd: input.cwd,
|
|
16729
|
-
spawnCwd: input.spawnCwd,
|
|
16730
|
-
timeoutMs: Duration.toMillis(timeout)
|
|
16731
|
-
}));
|
|
16732
|
-
}));
|
|
16733
|
-
}
|
|
16734
|
-
const runProcessCore = Effect.fn("processRunner.runProcessCore")(function* (spawner, input) {
|
|
16735
|
-
const maxOutputBytes = input.maxOutputBytes ?? DEFAULT_MAX_OUTPUT_BYTES$2;
|
|
16736
|
-
const outputMode = input.outputMode ?? "error";
|
|
16737
|
-
const truncatedMarker = input.truncatedMarker ?? "";
|
|
16738
|
-
const extendEnv = input.env !== void 0;
|
|
16739
|
-
const spawnCommand = yield* resolveSpawnCommand(input.command, input.args, input.env === void 0 ? {} : {
|
|
16740
|
-
env: input.env,
|
|
16741
|
-
extendEnv
|
|
16742
|
-
});
|
|
16743
|
-
const child = yield* spawner.spawn(ChildProcess$1.make(spawnCommand.command, spawnCommand.args, {
|
|
16744
|
-
...input.spawnCwd ?? input.cwd ? { cwd: input.spawnCwd ?? input.cwd } : {},
|
|
16745
|
-
...input.env !== void 0 ? {
|
|
16746
|
-
env: input.env,
|
|
16747
|
-
extendEnv
|
|
16748
|
-
} : {},
|
|
16749
|
-
shell: spawnCommand.shell
|
|
16750
|
-
})).pipe(Effect.mapError((cause) => new ProcessSpawnError({
|
|
16751
|
-
command: input.command,
|
|
16752
|
-
argumentCount: input.args.length,
|
|
16753
|
-
cwd: input.cwd,
|
|
16754
|
-
spawnCwd: input.spawnCwd,
|
|
16755
|
-
resolvedCommand: spawnCommand.command,
|
|
16756
|
-
resolvedArgumentCount: spawnCommand.args.length,
|
|
16757
|
-
shell: spawnCommand.shell,
|
|
16758
|
-
cause
|
|
16759
|
-
})));
|
|
16760
|
-
const stdin = input.stdin;
|
|
16761
|
-
const writeStdin = stdin === void 0 ? Effect.void : Stream.run(Stream.encodeText(Stream.make(stdin)), child.stdin).pipe(Effect.mapError((cause) => new ProcessStdinError({
|
|
16762
|
-
command: input.command,
|
|
16763
|
-
argumentCount: input.args.length,
|
|
16764
|
-
cwd: input.cwd,
|
|
16765
|
-
spawnCwd: input.spawnCwd,
|
|
16766
|
-
stdinBytes: Buffer.byteLength(stdin),
|
|
16767
|
-
cause
|
|
16768
|
-
})));
|
|
16769
|
-
const [stdout, stderr] = yield* Effect.all([
|
|
16770
|
-
collectText({
|
|
16771
|
-
command: input.command,
|
|
16772
|
-
args: input.args,
|
|
16773
|
-
cwd: input.cwd,
|
|
16774
|
-
spawnCwd: input.spawnCwd,
|
|
16775
|
-
streamName: "stdout",
|
|
16776
|
-
stream: child.stdout,
|
|
16777
|
-
maxOutputBytes,
|
|
16778
|
-
outputMode,
|
|
16779
|
-
truncatedMarker
|
|
16780
|
-
}),
|
|
16781
|
-
collectText({
|
|
16782
|
-
command: input.command,
|
|
16783
|
-
args: input.args,
|
|
16784
|
-
cwd: input.cwd,
|
|
16785
|
-
spawnCwd: input.spawnCwd,
|
|
16786
|
-
streamName: "stderr",
|
|
16787
|
-
stream: child.stderr,
|
|
16788
|
-
maxOutputBytes,
|
|
16789
|
-
outputMode,
|
|
16790
|
-
truncatedMarker
|
|
16791
|
-
}),
|
|
16792
|
-
writeStdin
|
|
16793
|
-
], { concurrency: "unbounded" });
|
|
16794
|
-
const exitCode = yield* child.exitCode.pipe(Effect.mapError((cause) => new ProcessReadError({
|
|
16795
|
-
command: input.command,
|
|
16796
|
-
argumentCount: input.args.length,
|
|
16797
|
-
cwd: input.cwd,
|
|
16798
|
-
spawnCwd: input.spawnCwd,
|
|
16799
|
-
stream: "exitCode",
|
|
16800
|
-
cause
|
|
16801
|
-
})));
|
|
16802
|
-
return {
|
|
16803
|
-
stdout: stdout.text,
|
|
16804
|
-
stderr: stderr.text,
|
|
16805
|
-
code: exitCode,
|
|
16806
|
-
timedOut: false,
|
|
16807
|
-
stdoutTruncated: stdout.truncated,
|
|
16808
|
-
stderrTruncated: stderr.truncated
|
|
16809
|
-
};
|
|
16810
|
-
});
|
|
16811
|
-
const make$58 = Effect.fn("ProcessRunner.make")(function* () {
|
|
16812
|
-
const spawner = yield* ChildProcessSpawner$1.ChildProcessSpawner;
|
|
16813
|
-
const run = (input) => finalizeRunProcess(runProcessCore(spawner, input), input);
|
|
16814
|
-
return ProcessRunner.of({ run });
|
|
16815
|
-
});
|
|
16816
|
-
const layer$53 = Layer.effect(ProcessRunner, make$58());
|
|
16817
|
-
//#endregion
|
|
16818
16965
|
//#region src/project/RepositoryIdentityResolver.ts
|
|
16819
16966
|
const DEFAULT_REPOSITORY_IDENTITY_CACHE_CAPACITY = 512;
|
|
16820
16967
|
const DEFAULT_POSITIVE_CACHE_TTL = Duration.minutes(1);
|
|
@@ -78248,104 +78395,6 @@ const serveCommand = Command.make("serve", { ...sharedServerCommandFlags }).pipe
|
|
|
78248
78395
|
forceAutoBootstrapProjectFromCwd: false
|
|
78249
78396
|
})));
|
|
78250
78397
|
//#endregion
|
|
78251
|
-
//#region src/cli/completions.ts
|
|
78252
|
-
/**
|
|
78253
|
-
* Installs the zsh completion script for the `p4` command.
|
|
78254
|
-
*
|
|
78255
|
-
* zsh ships `_perforce` with `#compdef p4 p4d`, so out of the box tabbing
|
|
78256
|
-
* through our subcommands prints "unhandled perforce command: …" from its
|
|
78257
|
-
* fallback message. `compinit` resolves duplicate `#compdef` claims by fpath
|
|
78258
|
-
* order — first match wins — and `_perforce` lives in the *last* fpath entry
|
|
78259
|
-
* (`/usr/share/zsh/<ver>/functions`). Writing our own `_p4` into any earlier
|
|
78260
|
-
* directory therefore replaces it outright, with no shell-config edit.
|
|
78261
|
-
*/
|
|
78262
|
-
const COMPLETION_FILE = "_p4";
|
|
78263
|
-
const GENERATE_TIMEOUT_MS = 1e4;
|
|
78264
|
-
/**
|
|
78265
|
-
* Directories ahead of `/usr/share/zsh/*` in a default fpath, most specific
|
|
78266
|
-
* first. `~/.zfunc` is the fallback and is the only one needing an fpath line.
|
|
78267
|
-
*/
|
|
78268
|
-
function candidateDirectories(input) {
|
|
78269
|
-
return [
|
|
78270
|
-
{
|
|
78271
|
-
dir: input.join(input.homeDir, ".oh-my-zsh", "custom", "completions"),
|
|
78272
|
-
requiresFpathEntry: false
|
|
78273
|
-
},
|
|
78274
|
-
{
|
|
78275
|
-
dir: "/opt/homebrew/share/zsh/site-functions",
|
|
78276
|
-
requiresFpathEntry: false
|
|
78277
|
-
},
|
|
78278
|
-
{
|
|
78279
|
-
dir: "/usr/local/share/zsh/site-functions",
|
|
78280
|
-
requiresFpathEntry: false
|
|
78281
|
-
},
|
|
78282
|
-
{
|
|
78283
|
-
dir: input.join(input.homeDir, ".zfunc"),
|
|
78284
|
-
requiresFpathEntry: true
|
|
78285
|
-
}
|
|
78286
|
-
];
|
|
78287
|
-
}
|
|
78288
|
-
/**
|
|
78289
|
-
* Asks the running CLI for its own completion script rather than rendering one
|
|
78290
|
-
* here, so the output cannot drift from what `--completions` produces.
|
|
78291
|
-
*/
|
|
78292
|
-
const generateZshCompletionScript = Effect.fn("cli.completions.generate")(function* () {
|
|
78293
|
-
const runner = yield* ProcessRunner;
|
|
78294
|
-
const execPath = yield* HostProcessExecutablePath;
|
|
78295
|
-
const entryPath = (yield* HostProcessArguments)[1] ?? "";
|
|
78296
|
-
if (entryPath === "") return null;
|
|
78297
|
-
const result = yield* runner.run({
|
|
78298
|
-
command: execPath,
|
|
78299
|
-
args: [
|
|
78300
|
-
entryPath,
|
|
78301
|
-
"--completions",
|
|
78302
|
-
"zsh"
|
|
78303
|
-
],
|
|
78304
|
-
timeout: GENERATE_TIMEOUT_MS
|
|
78305
|
-
});
|
|
78306
|
-
if (result.code !== 0) return null;
|
|
78307
|
-
const script = result.stdout.trim();
|
|
78308
|
-
return script.length === 0 ? null : `${script}\n`;
|
|
78309
|
-
});
|
|
78310
|
-
/**
|
|
78311
|
-
* Best-effort: returns null when completions are not applicable (Windows, no
|
|
78312
|
-
* writable directory) rather than failing the command that asked for them.
|
|
78313
|
-
* Installing completions is never the reason an install should fail.
|
|
78314
|
-
*/
|
|
78315
|
-
const installZshCompletion = Effect.fn("cli.completions.install")(function* () {
|
|
78316
|
-
if ((yield* HostProcessPlatform) === "win32") return null;
|
|
78317
|
-
const fs = yield* FileSystem.FileSystem;
|
|
78318
|
-
const path = yield* Path.Path;
|
|
78319
|
-
const homeDir = (yield* HostProcessEnvironment).HOME ?? "";
|
|
78320
|
-
if (homeDir === "") return null;
|
|
78321
|
-
const script = yield* generateZshCompletionScript();
|
|
78322
|
-
if (script === null) return null;
|
|
78323
|
-
for (const candidate of candidateDirectories({
|
|
78324
|
-
homeDir,
|
|
78325
|
-
join: path.join
|
|
78326
|
-
})) {
|
|
78327
|
-
if (candidate.requiresFpathEntry) {
|
|
78328
|
-
if (!(yield* fs.makeDirectory(candidate.dir, { recursive: true }).pipe(Effect.as(true), Effect.orElseSucceed(() => false)))) continue;
|
|
78329
|
-
} else if (!(yield* fs.exists(candidate.dir).pipe(Effect.orElseSucceed(() => false)))) continue;
|
|
78330
|
-
const target = path.join(candidate.dir, COMPLETION_FILE);
|
|
78331
|
-
if (!(yield* fs.writeFileString(target, script).pipe(Effect.as(true), Effect.orElseSucceed(() => false)))) continue;
|
|
78332
|
-
return {
|
|
78333
|
-
path: target,
|
|
78334
|
-
requiresFpathEntry: candidate.requiresFpathEntry
|
|
78335
|
-
};
|
|
78336
|
-
}
|
|
78337
|
-
return null;
|
|
78338
|
-
});
|
|
78339
|
-
function formatZshCompletionInstall(install) {
|
|
78340
|
-
const installed = `Installed zsh completions at ${install.path}.`;
|
|
78341
|
-
if (!install.requiresFpathEntry) return `${installed} Open a new shell to use them.`;
|
|
78342
|
-
return [
|
|
78343
|
-
installed,
|
|
78344
|
-
`Add this to ~/.zshrc before compinit runs, then open a new shell:`,
|
|
78345
|
-
` fpath=(${install.path.replace(/\/[^/]+$/u, "")} $fpath)`
|
|
78346
|
-
].join("\n");
|
|
78347
|
-
}
|
|
78348
|
-
//#endregion
|
|
78349
78398
|
//#region src/cli/service.ts
|
|
78350
78399
|
const isBootServiceCommandError = Schema$1.is(BootServiceCommandError);
|
|
78351
78400
|
const bootServiceLayer = (config) => layer$46({
|
|
@@ -78400,10 +78449,21 @@ const runServiceCommand = Effect.fn("cli.service.run")(function* (flags, run) {
|
|
|
78400
78449
|
if (baseDirNotice !== null) yield* Console.error(baseDirNotice);
|
|
78401
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));
|
|
78402
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
|
+
});
|
|
78403
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* () {
|
|
78404
78463
|
const result = yield* reconcileService();
|
|
78405
78464
|
if (!result.changed) {
|
|
78406
78465
|
yield* Console.log(`P4Code service is already installed with @p4code/cli@${version}.`);
|
|
78466
|
+
yield* reportZshCompletionInstall;
|
|
78407
78467
|
return;
|
|
78408
78468
|
}
|
|
78409
78469
|
yield* Console.log(formatReconcileSuccess({
|
|
@@ -78412,14 +78472,14 @@ const serviceInstallCommand = Command.make("install", projectLocationFlags).pipe
|
|
|
78412
78472
|
cliVersion: version,
|
|
78413
78473
|
platform: yield* HostProcessPlatform
|
|
78414
78474
|
}));
|
|
78415
|
-
|
|
78416
|
-
if (completion !== null) yield* Console.log(formatZshCompletionInstall(completion));
|
|
78475
|
+
yield* reportZshCompletionInstall;
|
|
78417
78476
|
}))));
|
|
78418
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* () {
|
|
78419
78478
|
const result = yield* reconcileService();
|
|
78420
78479
|
if (!result.changed) {
|
|
78421
78480
|
yield* (yield* BootService).restart;
|
|
78422
78481
|
yield* Console.log(`Restarted the P4Code service on @p4code/cli@${version}.`);
|
|
78482
|
+
yield* reportZshCompletionInstall;
|
|
78423
78483
|
return;
|
|
78424
78484
|
}
|
|
78425
78485
|
yield* Console.log(formatReconcileSuccess({
|
|
@@ -78428,6 +78488,7 @@ const serviceUpdateCommand = Command.make("update", projectLocationFlags).pipe(C
|
|
|
78428
78488
|
cliVersion: version,
|
|
78429
78489
|
platform: yield* HostProcessPlatform
|
|
78430
78490
|
}));
|
|
78491
|
+
yield* reportZshCompletionInstall;
|
|
78431
78492
|
}))));
|
|
78432
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* () {
|
|
78433
78494
|
const removed = yield* (yield* BootService).uninstall;
|
|
@@ -78476,11 +78537,14 @@ const reportUpdateAvailable = Effect.gen(function* () {
|
|
|
78476
78537
|
stateDir: paths.stateDir
|
|
78477
78538
|
});
|
|
78478
78539
|
if (reminder !== null) yield* Console.error(reminder);
|
|
78540
|
+
const completionsHint = yield* resolveMissingCompletionsHint({ stateDir: paths.stateDir });
|
|
78541
|
+
if (completionsHint !== null) yield* Console.error(completionsHint);
|
|
78479
78542
|
}).pipe(Effect.ignore);
|
|
78480
78543
|
const makeCli = () => Command.make("p4", { ...sharedServerCommandFlags }).pipe(Command.withDescription("Run the P4Code server."), Command.withHandler((flags) => runServerCommand(flags)), Command.withSubcommands([
|
|
78481
78544
|
startCommand,
|
|
78482
78545
|
serveCommand,
|
|
78483
78546
|
authCommand,
|
|
78547
|
+
completionsCommand,
|
|
78484
78548
|
projectCommand,
|
|
78485
78549
|
serviceCommand
|
|
78486
78550
|
]));
|