@p4pilot/core 0.1.0
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/browser.cjs +873 -0
- package/dist/browser.d.cts +3 -0
- package/dist/browser.d.ts +3 -0
- package/dist/browser.js +26 -0
- package/dist/changelist-BD8jDKA6.d.ts +123 -0
- package/dist/changelist-D-VWhdnq.d.cts +123 -0
- package/dist/chunk-HLG24B6W.js +387 -0
- package/dist/chunk-QIZ2GXWH.js +453 -0
- package/dist/index.cjs +566 -0
- package/dist/index.d.cts +25 -0
- package/dist/index.d.ts +25 -0
- package/dist/index.js +97 -0
- package/dist/p4-runner-CmZD1E6u.d.cts +69 -0
- package/dist/p4-runner-CmZD1E6u.d.ts +69 -0
- package/dist/testing/mock-runner.cjs +411 -0
- package/dist/testing/mock-runner.d.cts +48 -0
- package/dist/testing/mock-runner.d.ts +48 -0
- package/dist/testing/mock-runner.js +6 -0
- package/package.json +67 -0
package/dist/browser.cjs
ADDED
|
@@ -0,0 +1,873 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/browser.ts
|
|
21
|
+
var browser_exports = {};
|
|
22
|
+
__export(browser_exports, {
|
|
23
|
+
DEFAULT_ASSET_GUARD_CONFIG: () => DEFAULT_ASSET_GUARD_CONFIG,
|
|
24
|
+
MockP4Runner: () => MockP4Runner,
|
|
25
|
+
P4Client: () => P4Client,
|
|
26
|
+
P4PilotError: () => P4PilotError,
|
|
27
|
+
buildChangelistDescription: () => buildChangelistDescription,
|
|
28
|
+
classifyAsset: () => classifyAsset,
|
|
29
|
+
ensureOpenForEdit: () => ensureOpenForEdit,
|
|
30
|
+
ensureOpenForEditMany: () => ensureOpenForEditMany,
|
|
31
|
+
groupIndexed: () => groupIndexed,
|
|
32
|
+
parseZtag: () => parseZtag
|
|
33
|
+
});
|
|
34
|
+
module.exports = __toCommonJS(browser_exports);
|
|
35
|
+
|
|
36
|
+
// src/types.ts
|
|
37
|
+
var P4PilotError = class extends Error {
|
|
38
|
+
constructor(message, code, detail) {
|
|
39
|
+
super(message);
|
|
40
|
+
this.code = code;
|
|
41
|
+
this.detail = detail;
|
|
42
|
+
this.name = "P4PilotError";
|
|
43
|
+
}
|
|
44
|
+
code;
|
|
45
|
+
detail;
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
// src/ztag.ts
|
|
49
|
+
function parseZtag(stdout) {
|
|
50
|
+
const records = [];
|
|
51
|
+
let record = /* @__PURE__ */ new Map();
|
|
52
|
+
let previousKey;
|
|
53
|
+
const finishRecord = () => {
|
|
54
|
+
if (record.size > 0) {
|
|
55
|
+
records.push(record);
|
|
56
|
+
record = /* @__PURE__ */ new Map();
|
|
57
|
+
}
|
|
58
|
+
previousKey = void 0;
|
|
59
|
+
};
|
|
60
|
+
for (const line of stdout.split(/\r?\n/)) {
|
|
61
|
+
if (line.length === 0) {
|
|
62
|
+
finishRecord();
|
|
63
|
+
} else if (line.startsWith("... ")) {
|
|
64
|
+
const field = line.slice(4);
|
|
65
|
+
const separatorIndex = field.indexOf(" ");
|
|
66
|
+
const key = separatorIndex === -1 ? field : field.slice(0, separatorIndex);
|
|
67
|
+
const value = separatorIndex === -1 ? "" : field.slice(separatorIndex + 1);
|
|
68
|
+
record.set(key, value);
|
|
69
|
+
previousKey = key;
|
|
70
|
+
} else if (previousKey !== void 0) {
|
|
71
|
+
const previousValue = record.get(previousKey) ?? "";
|
|
72
|
+
record.set(previousKey, `${previousValue}
|
|
73
|
+
${line}`);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
finishRecord();
|
|
77
|
+
return records;
|
|
78
|
+
}
|
|
79
|
+
function groupIndexed(record) {
|
|
80
|
+
const grouped = {};
|
|
81
|
+
const indexed = /* @__PURE__ */ new Map();
|
|
82
|
+
for (const [key, value] of record) {
|
|
83
|
+
const match = /^(.*?)(\d+)$/.exec(key);
|
|
84
|
+
const baseKey = match?.[1];
|
|
85
|
+
const indexText = match?.[2];
|
|
86
|
+
if (baseKey === void 0 || indexText === void 0) {
|
|
87
|
+
grouped[key] = value;
|
|
88
|
+
continue;
|
|
89
|
+
}
|
|
90
|
+
const values = indexed.get(baseKey) ?? [];
|
|
91
|
+
values.push({ index: Number(indexText), value });
|
|
92
|
+
indexed.set(baseKey, values);
|
|
93
|
+
}
|
|
94
|
+
for (const [key, values] of indexed) {
|
|
95
|
+
grouped[key] = values.sort((left, right) => left.index - right.index).map(({ value }) => value);
|
|
96
|
+
}
|
|
97
|
+
return grouped;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// src/p4-client.ts
|
|
101
|
+
var CHANGE_CREATED = /Change (\d+) created/;
|
|
102
|
+
function num(value) {
|
|
103
|
+
return value === void 0 ? void 0 : Number(value);
|
|
104
|
+
}
|
|
105
|
+
function str(value) {
|
|
106
|
+
return typeof value === "string" ? value : void 0;
|
|
107
|
+
}
|
|
108
|
+
function asArray(value) {
|
|
109
|
+
if (value === void 0) return [];
|
|
110
|
+
return Array.isArray(value) ? value : [value];
|
|
111
|
+
}
|
|
112
|
+
function toOpenedFile(record) {
|
|
113
|
+
return {
|
|
114
|
+
depotFile: record.get("depotFile") ?? "",
|
|
115
|
+
clientFile: record.get("clientFile"),
|
|
116
|
+
rev: num(record.get("rev")),
|
|
117
|
+
action: record.get("action") ?? "edit",
|
|
118
|
+
change: record.get("change") ?? "default",
|
|
119
|
+
type: record.get("type") ?? "text"
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
function toFileStat(record) {
|
|
123
|
+
const headRev = num(record.get("headRev"));
|
|
124
|
+
const action = record.get("action");
|
|
125
|
+
return {
|
|
126
|
+
depotFile: record.get("depotFile") ?? "",
|
|
127
|
+
clientFile: record.get("clientFile"),
|
|
128
|
+
headType: record.get("headType"),
|
|
129
|
+
headRev,
|
|
130
|
+
haveRev: num(record.get("haveRev")),
|
|
131
|
+
action,
|
|
132
|
+
isOpened: action !== void 0,
|
|
133
|
+
isTracked: headRev !== void 0
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
function toChangelistSummary(record) {
|
|
137
|
+
const status = record.get("status");
|
|
138
|
+
return {
|
|
139
|
+
change: record.get("change") ?? "",
|
|
140
|
+
description: record.get("desc") ?? "",
|
|
141
|
+
status: status === "submitted" || status === "shelved" ? status : "pending",
|
|
142
|
+
user: record.get("user"),
|
|
143
|
+
client: record.get("client")
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
var P4Client = class {
|
|
147
|
+
#runner;
|
|
148
|
+
constructor(runner) {
|
|
149
|
+
this.#runner = runner;
|
|
150
|
+
}
|
|
151
|
+
async #run(args, opts) {
|
|
152
|
+
const result = await this.#runner.run(args, opts);
|
|
153
|
+
if (result.exitCode !== 0) {
|
|
154
|
+
throw new P4PilotError(
|
|
155
|
+
`p4 ${args.join(" ")} failed`,
|
|
156
|
+
"P4_COMMAND_FAILED",
|
|
157
|
+
result.stderr.trim() || result.stdout.trim()
|
|
158
|
+
);
|
|
159
|
+
}
|
|
160
|
+
return result;
|
|
161
|
+
}
|
|
162
|
+
async info() {
|
|
163
|
+
const { stdout } = await this.#run(["info"]);
|
|
164
|
+
const record = parseZtag(stdout)[0];
|
|
165
|
+
const info = {};
|
|
166
|
+
if (record) {
|
|
167
|
+
for (const [key, value] of record) {
|
|
168
|
+
info[key] = value;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
return info;
|
|
172
|
+
}
|
|
173
|
+
async opened(opts) {
|
|
174
|
+
const args = ["opened"];
|
|
175
|
+
if (opts?.changelist) args.push("-c", opts.changelist);
|
|
176
|
+
const { stdout } = await this.#run(args);
|
|
177
|
+
return parseZtag(stdout).map(toOpenedFile);
|
|
178
|
+
}
|
|
179
|
+
async fstat(files) {
|
|
180
|
+
if (files.length === 0) return [];
|
|
181
|
+
const { stdout } = await this.#run(["fstat", ...files]);
|
|
182
|
+
return parseZtag(stdout).map(toFileStat);
|
|
183
|
+
}
|
|
184
|
+
async edit(files, opts) {
|
|
185
|
+
return this.#openOp("edit", files, opts);
|
|
186
|
+
}
|
|
187
|
+
async add(files, opts) {
|
|
188
|
+
return this.#openOp("add", files, opts);
|
|
189
|
+
}
|
|
190
|
+
async deleteFiles(files, opts) {
|
|
191
|
+
return this.#openOp("delete", files, opts);
|
|
192
|
+
}
|
|
193
|
+
async #openOp(command, files, opts) {
|
|
194
|
+
if (files.length === 0) return [];
|
|
195
|
+
const args = [command];
|
|
196
|
+
if (opts?.changelist) args.push("-c", opts.changelist);
|
|
197
|
+
args.push(...files);
|
|
198
|
+
const { stdout } = await this.#run(args);
|
|
199
|
+
return parseZtag(stdout).map(toOpenedFile);
|
|
200
|
+
}
|
|
201
|
+
async revert(files) {
|
|
202
|
+
if (files.length === 0) return [];
|
|
203
|
+
const { stdout } = await this.#run(["revert", ...files]);
|
|
204
|
+
return parseZtag(stdout).map((record) => record.get("depotFile") ?? "").filter((depotFile) => depotFile.length > 0);
|
|
205
|
+
}
|
|
206
|
+
async sync(paths) {
|
|
207
|
+
const args = ["sync"];
|
|
208
|
+
if (paths && paths.length > 0) args.push(...paths);
|
|
209
|
+
const { stdout } = await this.#run(args);
|
|
210
|
+
return { synced: parseZtag(stdout).length };
|
|
211
|
+
}
|
|
212
|
+
async where(file) {
|
|
213
|
+
const { stdout } = await this.#run(["where", file]);
|
|
214
|
+
const record = parseZtag(stdout)[0];
|
|
215
|
+
if (!record) {
|
|
216
|
+
throw new P4PilotError(
|
|
217
|
+
`p4 where returned no mapping for ${file}`,
|
|
218
|
+
"FILE_NOT_IN_CLIENT"
|
|
219
|
+
);
|
|
220
|
+
}
|
|
221
|
+
const clientFile = record.get("clientFile") ?? "";
|
|
222
|
+
return {
|
|
223
|
+
depotFile: record.get("depotFile") ?? "",
|
|
224
|
+
clientFile,
|
|
225
|
+
path: record.get("path") ?? clientFile
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
async changes(opts) {
|
|
229
|
+
const args = ["changes"];
|
|
230
|
+
if (opts?.status) args.push("-s", opts.status);
|
|
231
|
+
if (opts?.max !== void 0) args.push("-m", String(opts.max));
|
|
232
|
+
if (opts?.user) args.push("-u", opts.user);
|
|
233
|
+
const { stdout } = await this.#run(args);
|
|
234
|
+
return parseZtag(stdout).map(toChangelistSummary);
|
|
235
|
+
}
|
|
236
|
+
async describe(change, opts) {
|
|
237
|
+
const args = ["describe"];
|
|
238
|
+
if (opts?.diff) args.push("-du");
|
|
239
|
+
args.push(change);
|
|
240
|
+
const { stdout } = await this.#run(args);
|
|
241
|
+
const record = parseZtag(stdout)[0];
|
|
242
|
+
if (!record) {
|
|
243
|
+
throw new P4PilotError(
|
|
244
|
+
`p4 describe returned nothing for ${change}`,
|
|
245
|
+
"P4_COMMAND_FAILED"
|
|
246
|
+
);
|
|
247
|
+
}
|
|
248
|
+
const grouped = groupIndexed(record);
|
|
249
|
+
const depotFiles = asArray(grouped.depotFile);
|
|
250
|
+
const actions = asArray(grouped.action);
|
|
251
|
+
const revs = asArray(grouped.rev);
|
|
252
|
+
const files = depotFiles.map((depotFile, index) => ({
|
|
253
|
+
depotFile,
|
|
254
|
+
action: actions[index] ?? "edit",
|
|
255
|
+
rev: revs[index] === void 0 ? void 0 : Number(revs[index])
|
|
256
|
+
}));
|
|
257
|
+
return {
|
|
258
|
+
change: str(grouped.change) ?? change,
|
|
259
|
+
description: str(grouped.desc) ?? "",
|
|
260
|
+
user: str(grouped.user),
|
|
261
|
+
files,
|
|
262
|
+
diff: str(grouped.diff)
|
|
263
|
+
};
|
|
264
|
+
}
|
|
265
|
+
async filelog(file, opts) {
|
|
266
|
+
const args = ["filelog"];
|
|
267
|
+
if (opts?.max !== void 0) args.push("-m", String(opts.max));
|
|
268
|
+
args.push(file);
|
|
269
|
+
const { stdout } = await this.#run(args);
|
|
270
|
+
const record = parseZtag(stdout)[0];
|
|
271
|
+
if (!record) return [];
|
|
272
|
+
const grouped = groupIndexed(record);
|
|
273
|
+
const revs = asArray(grouped.rev);
|
|
274
|
+
const changes = asArray(grouped.change);
|
|
275
|
+
const actions = asArray(grouped.action);
|
|
276
|
+
const users = asArray(grouped.user);
|
|
277
|
+
const descriptions = asArray(grouped.desc);
|
|
278
|
+
return revs.map((rev, index) => ({
|
|
279
|
+
rev: Number(rev),
|
|
280
|
+
change: changes[index] ?? "",
|
|
281
|
+
action: actions[index] ?? "edit",
|
|
282
|
+
user: users[index] ?? "",
|
|
283
|
+
description: descriptions[index] ?? ""
|
|
284
|
+
}));
|
|
285
|
+
}
|
|
286
|
+
async newChangelist(description) {
|
|
287
|
+
const indented = description.replace(/\n/g, "\n ");
|
|
288
|
+
const spec = `Change: new
|
|
289
|
+
|
|
290
|
+
Description:
|
|
291
|
+
${indented}
|
|
292
|
+
`;
|
|
293
|
+
const { stdout } = await this.#run(["change", "-i"], { input: spec });
|
|
294
|
+
const matched = CHANGE_CREATED.exec(stdout);
|
|
295
|
+
if (matched?.[1]) return matched[1];
|
|
296
|
+
const change = parseZtag(stdout)[0]?.get("change");
|
|
297
|
+
if (change) return change;
|
|
298
|
+
throw new P4PilotError(
|
|
299
|
+
"could not parse created changelist number",
|
|
300
|
+
"P4_COMMAND_FAILED",
|
|
301
|
+
stdout.trim()
|
|
302
|
+
);
|
|
303
|
+
}
|
|
304
|
+
async reopen(files, changelist) {
|
|
305
|
+
if (files.length === 0) return [];
|
|
306
|
+
const { stdout } = await this.#run(["reopen", "-c", changelist, ...files]);
|
|
307
|
+
return parseZtag(stdout).map(toOpenedFile);
|
|
308
|
+
}
|
|
309
|
+
};
|
|
310
|
+
|
|
311
|
+
// src/asset-guard.ts
|
|
312
|
+
var DEFAULT_ASSET_GUARD_CONFIG = {
|
|
313
|
+
largeAssetExtensions: [
|
|
314
|
+
".uasset",
|
|
315
|
+
".umap",
|
|
316
|
+
".fbx",
|
|
317
|
+
".psd",
|
|
318
|
+
".pak",
|
|
319
|
+
".mp4",
|
|
320
|
+
".mov",
|
|
321
|
+
".blend",
|
|
322
|
+
".exr"
|
|
323
|
+
],
|
|
324
|
+
binaryExtensions: [
|
|
325
|
+
".png",
|
|
326
|
+
".tga",
|
|
327
|
+
".jpg",
|
|
328
|
+
".jpeg",
|
|
329
|
+
".gif",
|
|
330
|
+
".bmp",
|
|
331
|
+
".dds",
|
|
332
|
+
".ico",
|
|
333
|
+
".wav",
|
|
334
|
+
".mp3",
|
|
335
|
+
".ogg",
|
|
336
|
+
".flac",
|
|
337
|
+
".bin",
|
|
338
|
+
".dll",
|
|
339
|
+
".exe",
|
|
340
|
+
".so",
|
|
341
|
+
".dylib",
|
|
342
|
+
".lib",
|
|
343
|
+
".a",
|
|
344
|
+
".zip",
|
|
345
|
+
".7z",
|
|
346
|
+
".rar",
|
|
347
|
+
".gz",
|
|
348
|
+
".pdf"
|
|
349
|
+
],
|
|
350
|
+
maxTextBytes: 1e6
|
|
351
|
+
};
|
|
352
|
+
function extname(path) {
|
|
353
|
+
const normalized = path.replace(/\\/g, "/");
|
|
354
|
+
const base = normalized.slice(normalized.lastIndexOf("/") + 1);
|
|
355
|
+
const dot = base.lastIndexOf(".");
|
|
356
|
+
return dot <= 0 ? "" : base.slice(dot).toLowerCase();
|
|
357
|
+
}
|
|
358
|
+
function isBinaryFiletype(filetype) {
|
|
359
|
+
if (filetype === void 0) return false;
|
|
360
|
+
return /(binary|ubinary|apple)/i.test(filetype);
|
|
361
|
+
}
|
|
362
|
+
function build(path, kind, filetype, sizeBytes, reason) {
|
|
363
|
+
return {
|
|
364
|
+
path,
|
|
365
|
+
kind,
|
|
366
|
+
filetype,
|
|
367
|
+
sizeBytes,
|
|
368
|
+
shouldRead: kind === "text",
|
|
369
|
+
reason
|
|
370
|
+
};
|
|
371
|
+
}
|
|
372
|
+
function classifyAsset(path, opts) {
|
|
373
|
+
const config = {
|
|
374
|
+
...DEFAULT_ASSET_GUARD_CONFIG,
|
|
375
|
+
...opts?.config
|
|
376
|
+
};
|
|
377
|
+
const ext = extname(path);
|
|
378
|
+
const filetype = opts?.stat?.headType;
|
|
379
|
+
const sizeBytes = opts?.sizeBytes;
|
|
380
|
+
if (config.largeAssetExtensions.includes(ext)) {
|
|
381
|
+
return build(
|
|
382
|
+
path,
|
|
383
|
+
"large-asset",
|
|
384
|
+
filetype,
|
|
385
|
+
sizeBytes,
|
|
386
|
+
`large-asset extension ${ext}`
|
|
387
|
+
);
|
|
388
|
+
}
|
|
389
|
+
if (isBinaryFiletype(filetype)) {
|
|
390
|
+
return build(
|
|
391
|
+
path,
|
|
392
|
+
"binary",
|
|
393
|
+
filetype,
|
|
394
|
+
sizeBytes,
|
|
395
|
+
`binary p4 filetype ${filetype ?? ""}`
|
|
396
|
+
);
|
|
397
|
+
}
|
|
398
|
+
if (config.binaryExtensions.includes(ext)) {
|
|
399
|
+
return build(
|
|
400
|
+
path,
|
|
401
|
+
"binary",
|
|
402
|
+
filetype,
|
|
403
|
+
sizeBytes,
|
|
404
|
+
`binary extension ${ext}`
|
|
405
|
+
);
|
|
406
|
+
}
|
|
407
|
+
if (sizeBytes !== void 0 && sizeBytes > config.maxTextBytes) {
|
|
408
|
+
return build(
|
|
409
|
+
path,
|
|
410
|
+
"binary",
|
|
411
|
+
filetype,
|
|
412
|
+
sizeBytes,
|
|
413
|
+
`size ${sizeBytes} exceeds maxTextBytes ${config.maxTextBytes}`
|
|
414
|
+
);
|
|
415
|
+
}
|
|
416
|
+
return build(
|
|
417
|
+
path,
|
|
418
|
+
"text",
|
|
419
|
+
filetype,
|
|
420
|
+
sizeBytes,
|
|
421
|
+
ext ? `text extension ${ext}` : "text file"
|
|
422
|
+
);
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
// src/auto-checkout.ts
|
|
426
|
+
async function ensureOpenForEdit(client, localPath, opts) {
|
|
427
|
+
const [stat] = await client.fstat([localPath]);
|
|
428
|
+
const asset = classifyAsset(localPath, { stat, config: opts?.assetConfig });
|
|
429
|
+
const changelist = opts?.changelist;
|
|
430
|
+
const openOpts = changelist === void 0 ? void 0 : { changelist };
|
|
431
|
+
if (stat?.isOpened) {
|
|
432
|
+
return {
|
|
433
|
+
path: localPath,
|
|
434
|
+
status: "already-open",
|
|
435
|
+
action: stat.action,
|
|
436
|
+
changelist,
|
|
437
|
+
asset
|
|
438
|
+
};
|
|
439
|
+
}
|
|
440
|
+
if (stat?.isTracked) {
|
|
441
|
+
const [opened] = await client.edit([localPath], openOpts);
|
|
442
|
+
return {
|
|
443
|
+
path: localPath,
|
|
444
|
+
status: "opened",
|
|
445
|
+
action: opened?.action ?? "edit",
|
|
446
|
+
changelist: opened?.change ?? changelist,
|
|
447
|
+
asset
|
|
448
|
+
};
|
|
449
|
+
}
|
|
450
|
+
const [added] = await client.add([localPath], openOpts);
|
|
451
|
+
return {
|
|
452
|
+
path: localPath,
|
|
453
|
+
status: "added",
|
|
454
|
+
action: added?.action ?? "add",
|
|
455
|
+
changelist: added?.change ?? changelist,
|
|
456
|
+
asset
|
|
457
|
+
};
|
|
458
|
+
}
|
|
459
|
+
async function ensureOpenForEditMany(client, localPaths, opts) {
|
|
460
|
+
const results = [];
|
|
461
|
+
for (const path of localPaths) {
|
|
462
|
+
try {
|
|
463
|
+
results.push(await ensureOpenForEdit(client, path, opts));
|
|
464
|
+
} catch {
|
|
465
|
+
results.push({ path, status: "skipped-untracked-ignored" });
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
return results;
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
// src/changelist.ts
|
|
472
|
+
function buildChangelistDescription(intent, prefix = "[p4pilot] ") {
|
|
473
|
+
const trimmed = intent.trim();
|
|
474
|
+
if (trimmed.length === 0) return prefix.trimEnd();
|
|
475
|
+
return trimmed.startsWith(prefix) ? trimmed : `${prefix}${trimmed}`;
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
// src/testing/mock-runner.ts
|
|
479
|
+
var success = (stdout = "") => ({
|
|
480
|
+
stdout,
|
|
481
|
+
stderr: "",
|
|
482
|
+
exitCode: 0
|
|
483
|
+
});
|
|
484
|
+
var failure = (stderr) => ({
|
|
485
|
+
stdout: "",
|
|
486
|
+
stderr,
|
|
487
|
+
exitCode: 1
|
|
488
|
+
});
|
|
489
|
+
function formatRecord(fields) {
|
|
490
|
+
return fields.filter(
|
|
491
|
+
(field) => field[1] !== void 0
|
|
492
|
+
).map(([key, rawValue]) => {
|
|
493
|
+
const [firstLine = "", ...continuationLines] = String(rawValue).split("\n");
|
|
494
|
+
return [`... ${key} ${firstLine}`, ...continuationLines].join("\n");
|
|
495
|
+
}).join("\n");
|
|
496
|
+
}
|
|
497
|
+
function formatRecords(records) {
|
|
498
|
+
if (records.length === 0) {
|
|
499
|
+
return "";
|
|
500
|
+
}
|
|
501
|
+
return `${records.map(formatRecord).join("\n\n")}
|
|
502
|
+
`;
|
|
503
|
+
}
|
|
504
|
+
function normalizePath(value) {
|
|
505
|
+
return value.replaceAll("\\", "/");
|
|
506
|
+
}
|
|
507
|
+
function relativePosix(from, to) {
|
|
508
|
+
const fromParts = from.replace(/\/+$/, "").split("/").filter(Boolean);
|
|
509
|
+
const toParts = to.split("/").filter(Boolean);
|
|
510
|
+
let i = 0;
|
|
511
|
+
while (i < fromParts.length && i < toParts.length && fromParts[i] === toParts[i])
|
|
512
|
+
i += 1;
|
|
513
|
+
const up = fromParts.slice(i).map(() => "..");
|
|
514
|
+
return [...up, ...toParts.slice(i)].join("/");
|
|
515
|
+
}
|
|
516
|
+
function parseCommandFiles(args) {
|
|
517
|
+
let change = "default";
|
|
518
|
+
const files = [];
|
|
519
|
+
for (let index = 1; index < args.length; index += 1) {
|
|
520
|
+
const argument = args[index];
|
|
521
|
+
if (argument === void 0) {
|
|
522
|
+
continue;
|
|
523
|
+
}
|
|
524
|
+
if (argument === "-c") {
|
|
525
|
+
change = args[index + 1] ?? "default";
|
|
526
|
+
index += 1;
|
|
527
|
+
} else if (!argument.startsWith("-")) {
|
|
528
|
+
files.push(argument);
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
return { change, files };
|
|
532
|
+
}
|
|
533
|
+
var MockP4Runner = class {
|
|
534
|
+
#state;
|
|
535
|
+
constructor(state) {
|
|
536
|
+
this.#state = state;
|
|
537
|
+
}
|
|
538
|
+
get state() {
|
|
539
|
+
return this.#state;
|
|
540
|
+
}
|
|
541
|
+
async run(args, opts) {
|
|
542
|
+
const commandArgs = args[0] === "-ztag" ? args.slice(1) : [...args];
|
|
543
|
+
const command = commandArgs[0];
|
|
544
|
+
switch (command) {
|
|
545
|
+
case "info":
|
|
546
|
+
return this.runInfo();
|
|
547
|
+
case "fstat":
|
|
548
|
+
return this.runFstat(commandArgs);
|
|
549
|
+
case "opened":
|
|
550
|
+
return this.runOpened(commandArgs);
|
|
551
|
+
case "edit":
|
|
552
|
+
return this.runEdit(commandArgs);
|
|
553
|
+
case "add":
|
|
554
|
+
return this.runAdd(commandArgs);
|
|
555
|
+
case "revert":
|
|
556
|
+
return this.runRevert(commandArgs);
|
|
557
|
+
case "where":
|
|
558
|
+
return this.runWhere(commandArgs);
|
|
559
|
+
case "changes":
|
|
560
|
+
return this.runChanges(commandArgs);
|
|
561
|
+
case "describe":
|
|
562
|
+
return this.runDescribe(commandArgs);
|
|
563
|
+
case "change":
|
|
564
|
+
return this.runChange(commandArgs, opts);
|
|
565
|
+
case "sync":
|
|
566
|
+
return this.runSync();
|
|
567
|
+
case "filelog":
|
|
568
|
+
return this.runFilelog(commandArgs);
|
|
569
|
+
default:
|
|
570
|
+
return failure(`Unsupported mock p4 command: ${command ?? ""}`);
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
runInfo() {
|
|
574
|
+
return success(
|
|
575
|
+
formatRecords([
|
|
576
|
+
[
|
|
577
|
+
["userName", this.#state.user],
|
|
578
|
+
["clientName", this.#state.client],
|
|
579
|
+
["clientRoot", this.#state.root],
|
|
580
|
+
["serverAddress", this.#state.port]
|
|
581
|
+
]
|
|
582
|
+
])
|
|
583
|
+
);
|
|
584
|
+
}
|
|
585
|
+
runFstat(args) {
|
|
586
|
+
const requested = args.slice(1).filter((argument) => !argument.startsWith("-"));
|
|
587
|
+
const files = requested.length === 0 ? this.#state.files : requested.flatMap((file) => this.findFiles(file));
|
|
588
|
+
return success(
|
|
589
|
+
formatRecords(files.map((file) => this.fileStatFields(file)))
|
|
590
|
+
);
|
|
591
|
+
}
|
|
592
|
+
runOpened(args) {
|
|
593
|
+
const { files } = parseCommandFiles(args);
|
|
594
|
+
const changeIndex = args.indexOf("-c");
|
|
595
|
+
const requestedChange = changeIndex === -1 ? void 0 : args[changeIndex + 1];
|
|
596
|
+
const openedFiles = this.#state.files.filter((file) => {
|
|
597
|
+
if (file.opened === void 0) {
|
|
598
|
+
return false;
|
|
599
|
+
}
|
|
600
|
+
if (requestedChange !== void 0 && file.opened.change !== requestedChange) {
|
|
601
|
+
return false;
|
|
602
|
+
}
|
|
603
|
+
return files.length === 0 || files.some((requested) => this.matches(file, requested));
|
|
604
|
+
});
|
|
605
|
+
return success(
|
|
606
|
+
formatRecords(openedFiles.map((file) => this.openedFields(file)))
|
|
607
|
+
);
|
|
608
|
+
}
|
|
609
|
+
runEdit(args) {
|
|
610
|
+
const { change, files } = parseCommandFiles(args);
|
|
611
|
+
const edited = [];
|
|
612
|
+
for (const requested of files) {
|
|
613
|
+
const file = this.findFile(requested);
|
|
614
|
+
if (file === void 0 || file.headRev === void 0) {
|
|
615
|
+
return failure(`${requested} - no such file(s).`);
|
|
616
|
+
}
|
|
617
|
+
file.opened = { action: "edit", change };
|
|
618
|
+
edited.push(file);
|
|
619
|
+
}
|
|
620
|
+
return success(
|
|
621
|
+
formatRecords(edited.map((file) => this.openedFields(file)))
|
|
622
|
+
);
|
|
623
|
+
}
|
|
624
|
+
runAdd(args) {
|
|
625
|
+
const { change, files } = parseCommandFiles(args);
|
|
626
|
+
const added = [];
|
|
627
|
+
for (const requested of files) {
|
|
628
|
+
let file = this.findFile(requested);
|
|
629
|
+
if (file === void 0) {
|
|
630
|
+
const clientFile = normalizePath(requested);
|
|
631
|
+
file = {
|
|
632
|
+
depotFile: this.toDepotFile(clientFile),
|
|
633
|
+
clientFile,
|
|
634
|
+
headType: "text"
|
|
635
|
+
};
|
|
636
|
+
this.#state.files.push(file);
|
|
637
|
+
}
|
|
638
|
+
file.opened = { action: "add", change };
|
|
639
|
+
added.push(file);
|
|
640
|
+
}
|
|
641
|
+
return success(formatRecords(added.map((file) => this.openedFields(file))));
|
|
642
|
+
}
|
|
643
|
+
runRevert(args) {
|
|
644
|
+
const { files } = parseCommandFiles(args);
|
|
645
|
+
const reverted = [];
|
|
646
|
+
for (const requested of files) {
|
|
647
|
+
const fileIndex = this.#state.files.findIndex(
|
|
648
|
+
(file2) => this.matches(file2, requested)
|
|
649
|
+
);
|
|
650
|
+
const file = this.#state.files[fileIndex];
|
|
651
|
+
if (file === void 0 || file.opened === void 0) {
|
|
652
|
+
continue;
|
|
653
|
+
}
|
|
654
|
+
reverted.push([["depotFile", file.depotFile]]);
|
|
655
|
+
if (file.opened.action === "add" && file.headRev === void 0) {
|
|
656
|
+
this.#state.files.splice(fileIndex, 1);
|
|
657
|
+
} else {
|
|
658
|
+
delete file.opened;
|
|
659
|
+
}
|
|
660
|
+
}
|
|
661
|
+
return success(formatRecords(reverted));
|
|
662
|
+
}
|
|
663
|
+
runWhere(args) {
|
|
664
|
+
const requested = args.find(
|
|
665
|
+
(argument, index) => index > 0 && !argument.startsWith("-")
|
|
666
|
+
);
|
|
667
|
+
const file = requested === void 0 ? void 0 : this.findFile(requested);
|
|
668
|
+
if (file === void 0) {
|
|
669
|
+
return failure(`${requested ?? ""} - file(s) not in client view.`);
|
|
670
|
+
}
|
|
671
|
+
return success(
|
|
672
|
+
formatRecords([
|
|
673
|
+
[
|
|
674
|
+
["depotFile", file.depotFile],
|
|
675
|
+
["clientFile", file.clientFile],
|
|
676
|
+
["path", file.clientFile]
|
|
677
|
+
]
|
|
678
|
+
])
|
|
679
|
+
);
|
|
680
|
+
}
|
|
681
|
+
runChanges(args) {
|
|
682
|
+
let status;
|
|
683
|
+
let max;
|
|
684
|
+
for (let index = 1; index < args.length; index += 1) {
|
|
685
|
+
if (args[index] === "-s") {
|
|
686
|
+
status = args[index + 1];
|
|
687
|
+
index += 1;
|
|
688
|
+
} else if (args[index] === "-m") {
|
|
689
|
+
max = Number(args[index + 1]);
|
|
690
|
+
index += 1;
|
|
691
|
+
}
|
|
692
|
+
}
|
|
693
|
+
let changelists = [...this.#state.changelists ?? []];
|
|
694
|
+
if (status !== void 0) {
|
|
695
|
+
changelists = changelists.filter(
|
|
696
|
+
(changelist) => changelist.status === status
|
|
697
|
+
);
|
|
698
|
+
}
|
|
699
|
+
if (max !== void 0 && Number.isFinite(max)) {
|
|
700
|
+
changelists = changelists.slice(0, max);
|
|
701
|
+
}
|
|
702
|
+
return success(
|
|
703
|
+
formatRecords(
|
|
704
|
+
changelists.map((changelist) => [
|
|
705
|
+
["change", changelist.change],
|
|
706
|
+
["desc", changelist.description],
|
|
707
|
+
["status", changelist.status],
|
|
708
|
+
["user", changelist.user],
|
|
709
|
+
["client", changelist.client]
|
|
710
|
+
])
|
|
711
|
+
)
|
|
712
|
+
);
|
|
713
|
+
}
|
|
714
|
+
runDescribe(args) {
|
|
715
|
+
const change = [...args].reverse().find((argument) => !argument.startsWith("-"));
|
|
716
|
+
const changelist = this.#state.changelists?.find(
|
|
717
|
+
(item) => item.change === change
|
|
718
|
+
);
|
|
719
|
+
if (changelist === void 0) {
|
|
720
|
+
return failure(`Change ${change ?? ""} unknown.`);
|
|
721
|
+
}
|
|
722
|
+
const fields = [
|
|
723
|
+
["change", changelist.change],
|
|
724
|
+
["desc", changelist.description],
|
|
725
|
+
["user", changelist.user],
|
|
726
|
+
["status", changelist.status]
|
|
727
|
+
];
|
|
728
|
+
for (const [index, depotFile] of (changelist.files ?? []).entries()) {
|
|
729
|
+
const file = this.findFile(depotFile);
|
|
730
|
+
fields.push([`depotFile${index}`, depotFile]);
|
|
731
|
+
fields.push([`action${index}`, file?.opened?.action ?? "edit"]);
|
|
732
|
+
fields.push([`rev${index}`, file?.headRev]);
|
|
733
|
+
}
|
|
734
|
+
return success(formatRecords([fields]));
|
|
735
|
+
}
|
|
736
|
+
runChange(args, opts) {
|
|
737
|
+
if (!args.includes("-i")) {
|
|
738
|
+
return failure("MockP4Runner only supports change -i.");
|
|
739
|
+
}
|
|
740
|
+
const changelists = this.#state.changelists ?? [];
|
|
741
|
+
this.#state.changelists = changelists;
|
|
742
|
+
const nextChange = String(
|
|
743
|
+
changelists.reduce((highest, changelist) => {
|
|
744
|
+
const value = Number(changelist.change);
|
|
745
|
+
return Number.isFinite(value) ? Math.max(highest, value) : highest;
|
|
746
|
+
}, 0) + 1
|
|
747
|
+
);
|
|
748
|
+
const description = this.parseDescription(opts?.input ?? "");
|
|
749
|
+
changelists.push({
|
|
750
|
+
change: nextChange,
|
|
751
|
+
description,
|
|
752
|
+
status: "pending",
|
|
753
|
+
user: this.#state.user,
|
|
754
|
+
client: this.#state.client,
|
|
755
|
+
files: []
|
|
756
|
+
});
|
|
757
|
+
return success(formatRecords([[["change", nextChange]]]));
|
|
758
|
+
}
|
|
759
|
+
runSync() {
|
|
760
|
+
const records = this.#state.files.filter((file) => file.headRev !== void 0).map((file) => [
|
|
761
|
+
["depotFile", file.depotFile],
|
|
762
|
+
["rev", file.headRev]
|
|
763
|
+
]);
|
|
764
|
+
return success(formatRecords(records));
|
|
765
|
+
}
|
|
766
|
+
runFilelog(args) {
|
|
767
|
+
const files = [];
|
|
768
|
+
for (let index = 1; index < args.length; index += 1) {
|
|
769
|
+
const argument = args[index];
|
|
770
|
+
if (argument === "-m") {
|
|
771
|
+
index += 1;
|
|
772
|
+
} else if (argument !== void 0 && !argument.startsWith("-")) {
|
|
773
|
+
files.push(argument);
|
|
774
|
+
}
|
|
775
|
+
}
|
|
776
|
+
const requested = files[0];
|
|
777
|
+
const file = requested === void 0 ? void 0 : this.findFile(requested);
|
|
778
|
+
if (file === void 0 || file.headRev === void 0) {
|
|
779
|
+
return failure(`${requested ?? ""} - no such file(s).`);
|
|
780
|
+
}
|
|
781
|
+
const fields = [
|
|
782
|
+
["depotFile", file.depotFile],
|
|
783
|
+
["rev0", file.headRev],
|
|
784
|
+
["change0", "1"],
|
|
785
|
+
["action0", "add"],
|
|
786
|
+
["user0", this.#state.user],
|
|
787
|
+
["desc0", "initial revision"]
|
|
788
|
+
];
|
|
789
|
+
return success(formatRecords([fields]));
|
|
790
|
+
}
|
|
791
|
+
fileStatFields(file) {
|
|
792
|
+
return [
|
|
793
|
+
["depotFile", file.depotFile],
|
|
794
|
+
["clientFile", file.clientFile],
|
|
795
|
+
["headType", file.headType],
|
|
796
|
+
["headRev", file.headRev],
|
|
797
|
+
["fileSize", file.sizeBytes],
|
|
798
|
+
["action", file.opened?.action],
|
|
799
|
+
["change", file.opened?.change]
|
|
800
|
+
];
|
|
801
|
+
}
|
|
802
|
+
openedFields(file) {
|
|
803
|
+
return [
|
|
804
|
+
["depotFile", file.depotFile],
|
|
805
|
+
["clientFile", file.clientFile],
|
|
806
|
+
["rev", file.headRev],
|
|
807
|
+
["action", file.opened?.action],
|
|
808
|
+
["change", file.opened?.change],
|
|
809
|
+
["type", file.headType ?? "text"]
|
|
810
|
+
];
|
|
811
|
+
}
|
|
812
|
+
findFiles(requested) {
|
|
813
|
+
return this.#state.files.filter((file) => this.matches(file, requested));
|
|
814
|
+
}
|
|
815
|
+
findFile(requested) {
|
|
816
|
+
return this.#state.files.find((file) => this.matches(file, requested));
|
|
817
|
+
}
|
|
818
|
+
matches(file, requested) {
|
|
819
|
+
const normalized = normalizePath(requested);
|
|
820
|
+
if (normalized.endsWith("/...")) {
|
|
821
|
+
const prefix = normalized.slice(0, -3);
|
|
822
|
+
return normalizePath(file.clientFile).startsWith(prefix) || file.depotFile.startsWith(prefix);
|
|
823
|
+
}
|
|
824
|
+
return normalizePath(file.clientFile) === normalized || file.depotFile === normalized;
|
|
825
|
+
}
|
|
826
|
+
toDepotFile(clientFile) {
|
|
827
|
+
const normalizedRoot = normalizePath(this.#state.root).replace(/\/$/, "");
|
|
828
|
+
const relative = relativePosix(normalizedRoot, clientFile);
|
|
829
|
+
const trackedFile = this.#state.files.find(
|
|
830
|
+
(file) => file.headRev !== void 0
|
|
831
|
+
);
|
|
832
|
+
if (trackedFile !== void 0) {
|
|
833
|
+
const trackedRelative = relativePosix(
|
|
834
|
+
normalizedRoot,
|
|
835
|
+
normalizePath(trackedFile.clientFile)
|
|
836
|
+
);
|
|
837
|
+
const suffix = `/${trackedRelative}`;
|
|
838
|
+
const depotRoot = trackedFile.depotFile.endsWith(suffix) ? trackedFile.depotFile.slice(0, -suffix.length) : "//depot";
|
|
839
|
+
return `${depotRoot}/${relative}`;
|
|
840
|
+
}
|
|
841
|
+
return `//depot/${relative}`;
|
|
842
|
+
}
|
|
843
|
+
parseDescription(input) {
|
|
844
|
+
const lines = input.split(/\r?\n/);
|
|
845
|
+
const descriptionIndex = lines.findIndex(
|
|
846
|
+
(line) => line.startsWith("Description:")
|
|
847
|
+
);
|
|
848
|
+
if (descriptionIndex === -1) {
|
|
849
|
+
return "";
|
|
850
|
+
}
|
|
851
|
+
const descriptionLines = [];
|
|
852
|
+
for (const line of lines.slice(descriptionIndex + 1)) {
|
|
853
|
+
if (/^[A-Za-z][A-Za-z0-9-]*:/.test(line)) {
|
|
854
|
+
break;
|
|
855
|
+
}
|
|
856
|
+
descriptionLines.push(line.replace(/^\t/, ""));
|
|
857
|
+
}
|
|
858
|
+
return descriptionLines.join("\n").trim();
|
|
859
|
+
}
|
|
860
|
+
};
|
|
861
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
862
|
+
0 && (module.exports = {
|
|
863
|
+
DEFAULT_ASSET_GUARD_CONFIG,
|
|
864
|
+
MockP4Runner,
|
|
865
|
+
P4Client,
|
|
866
|
+
P4PilotError,
|
|
867
|
+
buildChangelistDescription,
|
|
868
|
+
classifyAsset,
|
|
869
|
+
ensureOpenForEdit,
|
|
870
|
+
ensureOpenForEditMany,
|
|
871
|
+
groupIndexed,
|
|
872
|
+
parseZtag
|
|
873
|
+
});
|