@p4code/cli 0.0.37 → 0.0.39
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 +438 -372
- package/dist/client/assets/{DiffPanel-xkGxJH4k.js → DiffPanel-sDTPbG3j.js} +2 -2
- package/dist/client/assets/{FilePreviewPanel-CmJUrNYJ.js → FilePreviewPanel-DLWYdX4t.js} +2 -2
- package/dist/client/assets/{PreviewPanel-B5dlT-n2.js → PreviewPanel-BEu6pAZy.js} +2 -2
- package/dist/client/assets/{fileCommentAnnotations-BKadMjLJ.js → fileCommentAnnotations-Cy0LDixK.js} +2 -2
- package/dist/client/assets/{index-qr_OWE66.js → index-DCRvy1gc.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.39";
|
|
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,422 @@ 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
|
+
* Ordered by how reliably zsh actually scans them.
|
|
12320
|
+
*
|
|
12321
|
+
* oh-my-zsh comes first and is created on demand: it adds
|
|
12322
|
+
* `custom/completions` to fpath unconditionally, it is user-owned so
|
|
12323
|
+
* `compinit -i` never skips it as insecure, and it does not depend on a package
|
|
12324
|
+
* manager's prefix. The Homebrew and /usr/local directories are only used when
|
|
12325
|
+
* they already exist, and even then are not guaranteed to be on fpath —
|
|
12326
|
+
* `brew shellenv` contributes Homebrew's, and a plugin that rebuilds fpath can
|
|
12327
|
+
* drop it, which is exactly how a completion file lands somewhere zsh never
|
|
12328
|
+
* reads.
|
|
12329
|
+
*/
|
|
12330
|
+
function candidateDirectories(input) {
|
|
12331
|
+
return [
|
|
12332
|
+
{
|
|
12333
|
+
dir: input.join(input.homeDir, ".oh-my-zsh", "custom", "completions"),
|
|
12334
|
+
createWhenPresent: input.join(input.homeDir, ".oh-my-zsh")
|
|
12335
|
+
},
|
|
12336
|
+
{ dir: "/opt/homebrew/share/zsh/site-functions" },
|
|
12337
|
+
{ dir: "/usr/local/share/zsh/site-functions" },
|
|
12338
|
+
{
|
|
12339
|
+
dir: input.join(input.homeDir, ".zfunc"),
|
|
12340
|
+
requiresFpathEntry: true
|
|
12341
|
+
}
|
|
12342
|
+
];
|
|
12343
|
+
}
|
|
12344
|
+
/**
|
|
12345
|
+
* Asks the running CLI for its own completion script rather than rendering one
|
|
12346
|
+
* here, so the output cannot drift from what `--completions` produces.
|
|
12347
|
+
*/
|
|
12348
|
+
const generateZshCompletionScript = Effect.fn("cli.completions.generate")(function* () {
|
|
12349
|
+
const runner = yield* ProcessRunner;
|
|
12350
|
+
const execPath = yield* HostProcessExecutablePath;
|
|
12351
|
+
const entryPath = (yield* HostProcessArguments)[1] ?? "";
|
|
12352
|
+
if (entryPath === "") return null;
|
|
12353
|
+
const result = yield* runner.run({
|
|
12354
|
+
command: execPath,
|
|
12355
|
+
args: [
|
|
12356
|
+
entryPath,
|
|
12357
|
+
"--completions",
|
|
12358
|
+
"zsh"
|
|
12359
|
+
],
|
|
12360
|
+
timeout: GENERATE_TIMEOUT_MS
|
|
12361
|
+
});
|
|
12362
|
+
if (result.code !== 0) return null;
|
|
12363
|
+
const script = result.stdout.trim();
|
|
12364
|
+
return script.length === 0 ? null : `${script}\n`;
|
|
12365
|
+
});
|
|
12366
|
+
/**
|
|
12367
|
+
* Best-effort: returns null when completions are not applicable (Windows, no
|
|
12368
|
+
* writable directory) rather than failing the command that asked for them.
|
|
12369
|
+
* Installing completions is never the reason an install should fail.
|
|
12370
|
+
*/
|
|
12371
|
+
const installZshCompletion = Effect.fn("cli.completions.install")(function* () {
|
|
12372
|
+
if ((yield* HostProcessPlatform) === "win32") return null;
|
|
12373
|
+
const fs = yield* FileSystem.FileSystem;
|
|
12374
|
+
const path = yield* Path.Path;
|
|
12375
|
+
const homeDir = (yield* HostProcessEnvironment).HOME ?? "";
|
|
12376
|
+
if (homeDir === "") return null;
|
|
12377
|
+
const script = yield* generateZshCompletionScript();
|
|
12378
|
+
if (script === null) return null;
|
|
12379
|
+
for (const candidate of candidateDirectories({
|
|
12380
|
+
homeDir,
|
|
12381
|
+
join: path.join
|
|
12382
|
+
})) {
|
|
12383
|
+
if (candidate.requiresFpathEntry === true || candidate.createWhenPresent !== void 0 && (yield* fs.exists(candidate.createWhenPresent).pipe(Effect.orElseSucceed(() => false)))) {
|
|
12384
|
+
if (!(yield* fs.makeDirectory(candidate.dir, { recursive: true }).pipe(Effect.as(true), Effect.orElseSucceed(() => false)))) continue;
|
|
12385
|
+
} else if (!(yield* fs.exists(candidate.dir).pipe(Effect.orElseSucceed(() => false)))) continue;
|
|
12386
|
+
const target = path.join(candidate.dir, COMPLETION_FILE);
|
|
12387
|
+
if (!(yield* fs.writeFileString(target, script).pipe(Effect.as(true), Effect.orElseSucceed(() => false)))) continue;
|
|
12388
|
+
return {
|
|
12389
|
+
path: target,
|
|
12390
|
+
requiresFpathEntry: candidate.requiresFpathEntry === true
|
|
12391
|
+
};
|
|
12392
|
+
}
|
|
12393
|
+
return null;
|
|
12394
|
+
});
|
|
12395
|
+
const HINT_MARKER_FILE = "completions-hint";
|
|
12396
|
+
const HINT_INTERVAL_MS = 1440 * 60 * 1e3;
|
|
12397
|
+
const COMPLETIONS_HINT = "Shell completions are not installed, so tab completion shows Perforce's. Run `p4 completions install`.";
|
|
12398
|
+
/**
|
|
12399
|
+
* A hint when no `_p4` is on the fpath, at most once a day.
|
|
12400
|
+
*
|
|
12401
|
+
* A stat of a few directories, deliberately not the install itself: generating
|
|
12402
|
+
* the script spawns a subprocess, which is far too much to do on every command
|
|
12403
|
+
* just in case. Throttled through a marker file so an unactioned hint does not
|
|
12404
|
+
* become noise on every invocation.
|
|
12405
|
+
*/
|
|
12406
|
+
const resolveMissingCompletionsHint = Effect.fn("cli.completions.hint")(function* (input) {
|
|
12407
|
+
if ((yield* HostProcessPlatform) === "win32") return null;
|
|
12408
|
+
const fs = yield* FileSystem.FileSystem;
|
|
12409
|
+
const path = yield* Path.Path;
|
|
12410
|
+
const homeDir = (yield* HostProcessEnvironment).HOME ?? "";
|
|
12411
|
+
if (homeDir === "") return null;
|
|
12412
|
+
for (const candidate of candidateDirectories({
|
|
12413
|
+
homeDir,
|
|
12414
|
+
join: path.join
|
|
12415
|
+
})) if (yield* fs.exists(path.join(candidate.dir, COMPLETION_FILE)).pipe(Effect.orElseSucceed(() => false))) return null;
|
|
12416
|
+
const markerPath = path.join(input.stateDir, HINT_MARKER_FILE);
|
|
12417
|
+
const marker = yield* fs.stat(markerPath).pipe(Effect.asSome, Effect.orElseSucceed(() => null));
|
|
12418
|
+
const nowMs = DateTime.toEpochMillis(yield* DateTime.now);
|
|
12419
|
+
if (marker !== null && Option.isSome(marker)) {
|
|
12420
|
+
const mtime = marker.value.mtime;
|
|
12421
|
+
if (Option.isSome(mtime) && nowMs - mtime.value.getTime() < HINT_INTERVAL_MS) return null;
|
|
12422
|
+
}
|
|
12423
|
+
yield* fs.makeDirectory(input.stateDir, { recursive: true }).pipe(Effect.ignore);
|
|
12424
|
+
yield* fs.writeFileString(markerPath, "").pipe(Effect.ignore);
|
|
12425
|
+
return COMPLETIONS_HINT;
|
|
12426
|
+
});
|
|
12427
|
+
/**
|
|
12428
|
+
* Standalone so completions never depend on the service. A laptop that only
|
|
12429
|
+
* runs clients has no service to install, and a host whose `service install`
|
|
12430
|
+
* fails still deserves working completions.
|
|
12431
|
+
*/
|
|
12432
|
+
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* () {
|
|
12433
|
+
const install = yield* installZshCompletion();
|
|
12434
|
+
if (install === null) {
|
|
12435
|
+
yield* Console.error([
|
|
12436
|
+
"Could not install completions automatically.",
|
|
12437
|
+
"Write them yourself with:",
|
|
12438
|
+
" p4 --completions zsh > <a directory on your fpath>/_p4"
|
|
12439
|
+
].join("\n"));
|
|
12440
|
+
return;
|
|
12441
|
+
}
|
|
12442
|
+
yield* Console.log(formatZshCompletionInstall(install));
|
|
12443
|
+
}).pipe(Effect.provide(layer$53))))]));
|
|
12444
|
+
function formatZshCompletionInstall(install) {
|
|
12445
|
+
const installed = `Installed zsh completions at ${install.path}.`;
|
|
12446
|
+
if (!install.requiresFpathEntry) return `${installed} Open a new shell to use them.`;
|
|
12447
|
+
return [
|
|
12448
|
+
installed,
|
|
12449
|
+
`Add this to ~/.zshrc before compinit runs, then open a new shell:`,
|
|
12450
|
+
` fpath=(${install.path.replace(/\/[^/]+$/u, "")} $fpath)`
|
|
12451
|
+
].join("\n");
|
|
12452
|
+
}
|
|
12453
|
+
//#endregion
|
|
12038
12454
|
//#region src/orchestration/Services/OrchestrationEngine.ts
|
|
12039
12455
|
/**
|
|
12040
12456
|
* OrchestrationEngineService - Service tag for orchestration engine access.
|
|
@@ -16548,273 +16964,6 @@ function mergeGitStatusParts(local, remote) {
|
|
|
16548
16964
|
};
|
|
16549
16965
|
}
|
|
16550
16966
|
//#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
16967
|
//#region src/project/RepositoryIdentityResolver.ts
|
|
16819
16968
|
const DEFAULT_REPOSITORY_IDENTITY_CACHE_CAPACITY = 512;
|
|
16820
16969
|
const DEFAULT_POSITIVE_CACHE_TTL = Duration.minutes(1);
|
|
@@ -78248,104 +78397,6 @@ const serveCommand = Command.make("serve", { ...sharedServerCommandFlags }).pipe
|
|
|
78248
78397
|
forceAutoBootstrapProjectFromCwd: false
|
|
78249
78398
|
})));
|
|
78250
78399
|
//#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
78400
|
//#region src/cli/service.ts
|
|
78350
78401
|
const isBootServiceCommandError = Schema$1.is(BootServiceCommandError);
|
|
78351
78402
|
const bootServiceLayer = (config) => layer$46({
|
|
@@ -78400,10 +78451,21 @@ const runServiceCommand = Effect.fn("cli.service.run")(function* (flags, run) {
|
|
|
78400
78451
|
if (baseDirNotice !== null) yield* Console.error(baseDirNotice);
|
|
78401
78452
|
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
78453
|
});
|
|
78454
|
+
/**
|
|
78455
|
+
* Runs on every `service install` and `service update`, including the
|
|
78456
|
+
* already-current paths. Gating it on "the unit changed" meant a host whose
|
|
78457
|
+
* service was already installed — the common case after the first setup — never
|
|
78458
|
+
* got completions at all.
|
|
78459
|
+
*/
|
|
78460
|
+
const reportZshCompletionInstall = Effect.gen(function* () {
|
|
78461
|
+
const completion = yield* installZshCompletion().pipe(Effect.orElseSucceed(() => null));
|
|
78462
|
+
if (completion !== null) yield* Console.log(formatZshCompletionInstall(completion));
|
|
78463
|
+
});
|
|
78403
78464
|
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
78465
|
const result = yield* reconcileService();
|
|
78405
78466
|
if (!result.changed) {
|
|
78406
78467
|
yield* Console.log(`P4Code service is already installed with @p4code/cli@${version}.`);
|
|
78468
|
+
yield* reportZshCompletionInstall;
|
|
78407
78469
|
return;
|
|
78408
78470
|
}
|
|
78409
78471
|
yield* Console.log(formatReconcileSuccess({
|
|
@@ -78412,14 +78474,14 @@ const serviceInstallCommand = Command.make("install", projectLocationFlags).pipe
|
|
|
78412
78474
|
cliVersion: version,
|
|
78413
78475
|
platform: yield* HostProcessPlatform
|
|
78414
78476
|
}));
|
|
78415
|
-
|
|
78416
|
-
if (completion !== null) yield* Console.log(formatZshCompletionInstall(completion));
|
|
78477
|
+
yield* reportZshCompletionInstall;
|
|
78417
78478
|
}))));
|
|
78418
78479
|
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
78480
|
const result = yield* reconcileService();
|
|
78420
78481
|
if (!result.changed) {
|
|
78421
78482
|
yield* (yield* BootService).restart;
|
|
78422
78483
|
yield* Console.log(`Restarted the P4Code service on @p4code/cli@${version}.`);
|
|
78484
|
+
yield* reportZshCompletionInstall;
|
|
78423
78485
|
return;
|
|
78424
78486
|
}
|
|
78425
78487
|
yield* Console.log(formatReconcileSuccess({
|
|
@@ -78428,6 +78490,7 @@ const serviceUpdateCommand = Command.make("update", projectLocationFlags).pipe(C
|
|
|
78428
78490
|
cliVersion: version,
|
|
78429
78491
|
platform: yield* HostProcessPlatform
|
|
78430
78492
|
}));
|
|
78493
|
+
yield* reportZshCompletionInstall;
|
|
78431
78494
|
}))));
|
|
78432
78495
|
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
78496
|
const removed = yield* (yield* BootService).uninstall;
|
|
@@ -78476,11 +78539,14 @@ const reportUpdateAvailable = Effect.gen(function* () {
|
|
|
78476
78539
|
stateDir: paths.stateDir
|
|
78477
78540
|
});
|
|
78478
78541
|
if (reminder !== null) yield* Console.error(reminder);
|
|
78542
|
+
const completionsHint = yield* resolveMissingCompletionsHint({ stateDir: paths.stateDir });
|
|
78543
|
+
if (completionsHint !== null) yield* Console.error(completionsHint);
|
|
78479
78544
|
}).pipe(Effect.ignore);
|
|
78480
78545
|
const makeCli = () => Command.make("p4", { ...sharedServerCommandFlags }).pipe(Command.withDescription("Run the P4Code server."), Command.withHandler((flags) => runServerCommand(flags)), Command.withSubcommands([
|
|
78481
78546
|
startCommand,
|
|
78482
78547
|
serveCommand,
|
|
78483
78548
|
authCommand,
|
|
78549
|
+
completionsCommand,
|
|
78484
78550
|
projectCommand,
|
|
78485
78551
|
serviceCommand
|
|
78486
78552
|
]));
|