@fractary/codex 0.7.1 → 0.9.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/index.cjs +724 -116
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +570 -48
- package/dist/index.d.ts +570 -48
- package/dist/index.js +707 -117
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -5,14 +5,33 @@ var path3 = require('path');
|
|
|
5
5
|
var child_process = require('child_process');
|
|
6
6
|
var zod = require('zod');
|
|
7
7
|
var yaml = require('js-yaml');
|
|
8
|
-
var
|
|
8
|
+
var fs3 = require('fs/promises');
|
|
9
|
+
var util = require('util');
|
|
9
10
|
|
|
10
11
|
function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
|
|
11
12
|
|
|
13
|
+
function _interopNamespace(e) {
|
|
14
|
+
if (e && e.__esModule) return e;
|
|
15
|
+
var n = Object.create(null);
|
|
16
|
+
if (e) {
|
|
17
|
+
Object.keys(e).forEach(function (k) {
|
|
18
|
+
if (k !== 'default') {
|
|
19
|
+
var d = Object.getOwnPropertyDescriptor(e, k);
|
|
20
|
+
Object.defineProperty(n, k, d.get ? d : {
|
|
21
|
+
enumerable: true,
|
|
22
|
+
get: function () { return e[k]; }
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
n.default = e;
|
|
28
|
+
return Object.freeze(n);
|
|
29
|
+
}
|
|
30
|
+
|
|
12
31
|
var micromatch3__default = /*#__PURE__*/_interopDefault(micromatch3);
|
|
13
|
-
var
|
|
32
|
+
var path3__namespace = /*#__PURE__*/_interopNamespace(path3);
|
|
14
33
|
var yaml__default = /*#__PURE__*/_interopDefault(yaml);
|
|
15
|
-
var
|
|
34
|
+
var fs3__namespace = /*#__PURE__*/_interopNamespace(fs3);
|
|
16
35
|
|
|
17
36
|
var __defProp = Object.defineProperty;
|
|
18
37
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
@@ -169,7 +188,7 @@ function validatePath(filePath) {
|
|
|
169
188
|
return false;
|
|
170
189
|
}
|
|
171
190
|
}
|
|
172
|
-
const normalized =
|
|
191
|
+
const normalized = path3__namespace.default.normalize(filePath);
|
|
173
192
|
if (normalized.startsWith("..") || normalized.includes("/../") || normalized.includes("\\..\\")) {
|
|
174
193
|
return false;
|
|
175
194
|
}
|
|
@@ -187,7 +206,7 @@ function sanitizePath(filePath) {
|
|
|
187
206
|
}
|
|
188
207
|
sanitized = sanitized.replace(/^\/+/, "");
|
|
189
208
|
sanitized = sanitized.replace(/^~\//, "");
|
|
190
|
-
sanitized =
|
|
209
|
+
sanitized = path3__namespace.default.normalize(sanitized);
|
|
191
210
|
sanitized = sanitized.replace(/\.\.\//g, "").replace(/\.\./g, "");
|
|
192
211
|
sanitized = sanitized.replace(/^\.\//, "");
|
|
193
212
|
sanitized = sanitized.replace(/\0/g, "");
|
|
@@ -288,10 +307,10 @@ function parseReference(uri, options = {}) {
|
|
|
288
307
|
path: filePath
|
|
289
308
|
};
|
|
290
309
|
}
|
|
291
|
-
function buildUri(org, project,
|
|
310
|
+
function buildUri(org, project, path7) {
|
|
292
311
|
const base = `${CODEX_URI_PREFIX}${org}/${project}`;
|
|
293
|
-
if (
|
|
294
|
-
const cleanPath =
|
|
312
|
+
if (path7) {
|
|
313
|
+
const cleanPath = path7.startsWith("/") ? path7.slice(1) : path7;
|
|
295
314
|
return `${base}/${cleanPath}`;
|
|
296
315
|
}
|
|
297
316
|
return base;
|
|
@@ -362,7 +381,7 @@ function getCurrentContext(options = {}) {
|
|
|
362
381
|
return detectCurrentProject(options.cwd);
|
|
363
382
|
}
|
|
364
383
|
function calculateCachePath(org, project, filePath, cacheDir) {
|
|
365
|
-
return
|
|
384
|
+
return path3__namespace.default.join(cacheDir, org, project, filePath);
|
|
366
385
|
}
|
|
367
386
|
function resolveReference(uri, options = {}) {
|
|
368
387
|
const parsed = parseReference(uri);
|
|
@@ -371,7 +390,7 @@ function resolveReference(uri, options = {}) {
|
|
|
371
390
|
}
|
|
372
391
|
const cacheDir = options.cacheDir || DEFAULT_CACHE_DIR;
|
|
373
392
|
const currentContext = getCurrentContext(options);
|
|
374
|
-
const cachePath = parsed.path ? calculateCachePath(parsed.org, parsed.project, parsed.path, cacheDir) :
|
|
393
|
+
const cachePath = parsed.path ? calculateCachePath(parsed.org, parsed.project, parsed.path, cacheDir) : path3__namespace.default.join(cacheDir, parsed.org, parsed.project);
|
|
375
394
|
const isCurrentProject = currentContext.org === parsed.org && currentContext.project === parsed.project;
|
|
376
395
|
const resolved = {
|
|
377
396
|
...parsed,
|
|
@@ -380,9 +399,45 @@ function resolveReference(uri, options = {}) {
|
|
|
380
399
|
};
|
|
381
400
|
if (isCurrentProject && parsed.path) {
|
|
382
401
|
resolved.localPath = parsed.path;
|
|
402
|
+
if (options.config) {
|
|
403
|
+
const fileSource = detectFilePluginSource(parsed.path, options.config);
|
|
404
|
+
if (fileSource) {
|
|
405
|
+
resolved.sourceType = "file-plugin";
|
|
406
|
+
resolved.filePluginSource = fileSource.name;
|
|
407
|
+
resolved.localPath = fileSource.fullPath;
|
|
408
|
+
}
|
|
409
|
+
}
|
|
383
410
|
}
|
|
384
411
|
return resolved;
|
|
385
412
|
}
|
|
413
|
+
function detectFilePluginSource(filePath, config) {
|
|
414
|
+
if (!config?.file?.sources) {
|
|
415
|
+
return null;
|
|
416
|
+
}
|
|
417
|
+
for (const [sourceName, source] of Object.entries(config.file.sources)) {
|
|
418
|
+
const basePath = source.local?.base_path;
|
|
419
|
+
if (!basePath) {
|
|
420
|
+
continue;
|
|
421
|
+
}
|
|
422
|
+
const normalizedPath = filePath.replace(/^\.\//, "").replace(/^\//, "");
|
|
423
|
+
const normalizedBasePath = basePath.replace(/^\.\//, "").replace(/^\//, "").replace(/\/$/, "");
|
|
424
|
+
if (normalizedPath.startsWith(normalizedBasePath)) {
|
|
425
|
+
return {
|
|
426
|
+
name: sourceName,
|
|
427
|
+
fullPath: path3__namespace.default.join(basePath, normalizedPath.substring(normalizedBasePath.length).replace(/^\//, ""))
|
|
428
|
+
};
|
|
429
|
+
}
|
|
430
|
+
const sourceNameInPath = normalizedBasePath.split("/").pop();
|
|
431
|
+
if (sourceNameInPath && normalizedPath.startsWith(sourceNameInPath + "/")) {
|
|
432
|
+
const pathWithoutSource = normalizedPath.substring(sourceNameInPath.length + 1);
|
|
433
|
+
return {
|
|
434
|
+
name: sourceName,
|
|
435
|
+
fullPath: path3__namespace.default.join(basePath, pathWithoutSource)
|
|
436
|
+
};
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
return null;
|
|
440
|
+
}
|
|
386
441
|
function resolveReferences(uris, options = {}) {
|
|
387
442
|
return uris.map((uri) => resolveReference(uri, options)).filter((r) => r !== null);
|
|
388
443
|
}
|
|
@@ -396,9 +451,9 @@ function getRelativeCachePath(uri) {
|
|
|
396
451
|
return null;
|
|
397
452
|
}
|
|
398
453
|
if (parsed.path) {
|
|
399
|
-
return
|
|
454
|
+
return path3__namespace.default.join(parsed.org, parsed.project, parsed.path);
|
|
400
455
|
}
|
|
401
|
-
return
|
|
456
|
+
return path3__namespace.default.join(parsed.org, parsed.project);
|
|
402
457
|
}
|
|
403
458
|
|
|
404
459
|
// src/types/built-in.ts
|
|
@@ -843,6 +898,16 @@ var DirectionalSyncSchema = zod.z.object({
|
|
|
843
898
|
default_to_codex: zod.z.array(zod.z.string()).optional(),
|
|
844
899
|
default_from_codex: zod.z.array(zod.z.string()).optional()
|
|
845
900
|
});
|
|
901
|
+
var ArchiveProjectConfigSchema = zod.z.object({
|
|
902
|
+
enabled: zod.z.boolean(),
|
|
903
|
+
handler: zod.z.enum(["s3", "r2", "gcs", "local"]),
|
|
904
|
+
bucket: zod.z.string().optional(),
|
|
905
|
+
prefix: zod.z.string().optional(),
|
|
906
|
+
patterns: zod.z.array(zod.z.string()).optional()
|
|
907
|
+
});
|
|
908
|
+
var ArchiveConfigSchema = zod.z.object({
|
|
909
|
+
projects: zod.z.record(ArchiveProjectConfigSchema)
|
|
910
|
+
});
|
|
846
911
|
var CodexConfigSchema = zod.z.object({
|
|
847
912
|
organizationSlug: zod.z.string(),
|
|
848
913
|
directories: zod.z.object({
|
|
@@ -852,8 +917,45 @@ var CodexConfigSchema = zod.z.object({
|
|
|
852
917
|
}).optional(),
|
|
853
918
|
rules: SyncRulesSchema.optional(),
|
|
854
919
|
// Directional sync configuration
|
|
855
|
-
sync: DirectionalSyncSchema.optional()
|
|
920
|
+
sync: DirectionalSyncSchema.optional(),
|
|
921
|
+
// Archive configuration
|
|
922
|
+
archive: ArchiveConfigSchema.optional()
|
|
856
923
|
}).strict();
|
|
924
|
+
var FileSourceSchema = zod.z.object({
|
|
925
|
+
type: zod.z.enum(["s3", "r2", "gcs", "local"]),
|
|
926
|
+
bucket: zod.z.string().optional(),
|
|
927
|
+
prefix: zod.z.string().optional(),
|
|
928
|
+
region: zod.z.string().optional(),
|
|
929
|
+
local: zod.z.object({
|
|
930
|
+
base_path: zod.z.string()
|
|
931
|
+
}),
|
|
932
|
+
push: zod.z.object({
|
|
933
|
+
compress: zod.z.boolean().optional(),
|
|
934
|
+
keep_local: zod.z.boolean().optional()
|
|
935
|
+
}).optional(),
|
|
936
|
+
auth: zod.z.object({
|
|
937
|
+
profile: zod.z.string().optional()
|
|
938
|
+
}).optional()
|
|
939
|
+
}).refine(
|
|
940
|
+
(data) => {
|
|
941
|
+
if (data.type !== "local" && !data.bucket) {
|
|
942
|
+
return false;
|
|
943
|
+
}
|
|
944
|
+
return true;
|
|
945
|
+
},
|
|
946
|
+
{
|
|
947
|
+
message: "Bucket is required for s3, r2, and gcs storage types",
|
|
948
|
+
path: ["bucket"]
|
|
949
|
+
}
|
|
950
|
+
);
|
|
951
|
+
var FileConfigSchema = zod.z.object({
|
|
952
|
+
schema_version: zod.z.string(),
|
|
953
|
+
sources: zod.z.record(FileSourceSchema)
|
|
954
|
+
});
|
|
955
|
+
zod.z.object({
|
|
956
|
+
file: FileConfigSchema.optional(),
|
|
957
|
+
codex: CodexConfigSchema.optional()
|
|
958
|
+
});
|
|
857
959
|
function parseMetadata(content, options = {}) {
|
|
858
960
|
const { strict = true, normalize = true } = options;
|
|
859
961
|
const normalizedContent = normalize ? content.replace(/\r\n/g, "\n") : content;
|
|
@@ -977,11 +1079,17 @@ function getDefaultRules() {
|
|
|
977
1079
|
defaultExclude: []
|
|
978
1080
|
};
|
|
979
1081
|
}
|
|
1082
|
+
function getDefaultArchiveConfig() {
|
|
1083
|
+
return {
|
|
1084
|
+
projects: {}
|
|
1085
|
+
};
|
|
1086
|
+
}
|
|
980
1087
|
function getDefaultConfig(orgSlug) {
|
|
981
1088
|
return {
|
|
982
1089
|
organizationSlug: orgSlug,
|
|
983
1090
|
directories: getDefaultDirectories(orgSlug),
|
|
984
|
-
rules: getDefaultRules()
|
|
1091
|
+
rules: getDefaultRules(),
|
|
1092
|
+
archive: getDefaultArchiveConfig()
|
|
985
1093
|
};
|
|
986
1094
|
}
|
|
987
1095
|
|
|
@@ -1177,18 +1285,18 @@ function parseCustomDestination(value) {
|
|
|
1177
1285
|
);
|
|
1178
1286
|
}
|
|
1179
1287
|
const repo = value.substring(0, colonIndex).trim();
|
|
1180
|
-
const
|
|
1288
|
+
const path7 = value.substring(colonIndex + 1).trim();
|
|
1181
1289
|
if (!repo) {
|
|
1182
1290
|
throw new ValidationError(
|
|
1183
1291
|
`Invalid custom destination: repository name cannot be empty in "${value}"`
|
|
1184
1292
|
);
|
|
1185
1293
|
}
|
|
1186
|
-
if (!
|
|
1294
|
+
if (!path7) {
|
|
1187
1295
|
throw new ValidationError(
|
|
1188
1296
|
`Invalid custom destination: path cannot be empty in "${value}"`
|
|
1189
1297
|
);
|
|
1190
1298
|
}
|
|
1191
|
-
return { repo, path:
|
|
1299
|
+
return { repo, path: path7 };
|
|
1192
1300
|
}
|
|
1193
1301
|
function getCustomSyncDestinations(metadata) {
|
|
1194
1302
|
const customDestinations = metadata.codex_sync_custom;
|
|
@@ -1224,8 +1332,8 @@ function mergeFetchOptions(options) {
|
|
|
1224
1332
|
...options
|
|
1225
1333
|
};
|
|
1226
1334
|
}
|
|
1227
|
-
function detectContentType(
|
|
1228
|
-
const ext =
|
|
1335
|
+
function detectContentType(path7) {
|
|
1336
|
+
const ext = path7.split(".").pop()?.toLowerCase();
|
|
1229
1337
|
const mimeTypes = {
|
|
1230
1338
|
md: "text/markdown",
|
|
1231
1339
|
markdown: "text/markdown",
|
|
@@ -1285,19 +1393,19 @@ var LocalStorage = class {
|
|
|
1285
1393
|
if (!reference.localPath) {
|
|
1286
1394
|
throw new Error(`No local path for reference: ${reference.uri}`);
|
|
1287
1395
|
}
|
|
1288
|
-
const fullPath =
|
|
1396
|
+
const fullPath = path3__namespace.default.isAbsolute(reference.localPath) ? reference.localPath : path3__namespace.default.join(this.baseDir, reference.localPath);
|
|
1289
1397
|
try {
|
|
1290
|
-
await
|
|
1398
|
+
await fs3__namespace.default.access(fullPath);
|
|
1291
1399
|
} catch {
|
|
1292
1400
|
throw new Error(`File not found: ${fullPath}`);
|
|
1293
1401
|
}
|
|
1294
|
-
const stats = await
|
|
1402
|
+
const stats = await fs3__namespace.default.stat(fullPath);
|
|
1295
1403
|
if (stats.size > opts.maxSize) {
|
|
1296
1404
|
throw new Error(
|
|
1297
1405
|
`File too large: ${stats.size} bytes (max: ${opts.maxSize} bytes)`
|
|
1298
1406
|
);
|
|
1299
1407
|
}
|
|
1300
|
-
const content = await
|
|
1408
|
+
const content = await fs3__namespace.default.readFile(fullPath);
|
|
1301
1409
|
return {
|
|
1302
1410
|
content,
|
|
1303
1411
|
contentType: detectContentType(reference.localPath),
|
|
@@ -1316,9 +1424,9 @@ var LocalStorage = class {
|
|
|
1316
1424
|
if (!reference.localPath) {
|
|
1317
1425
|
return false;
|
|
1318
1426
|
}
|
|
1319
|
-
const fullPath =
|
|
1427
|
+
const fullPath = path3__namespace.default.isAbsolute(reference.localPath) ? reference.localPath : path3__namespace.default.join(this.baseDir, reference.localPath);
|
|
1320
1428
|
try {
|
|
1321
|
-
await
|
|
1429
|
+
await fs3__namespace.default.access(fullPath);
|
|
1322
1430
|
return true;
|
|
1323
1431
|
} catch {
|
|
1324
1432
|
return false;
|
|
@@ -1328,24 +1436,24 @@ var LocalStorage = class {
|
|
|
1328
1436
|
* Read file content as string
|
|
1329
1437
|
*/
|
|
1330
1438
|
async readText(filePath) {
|
|
1331
|
-
const fullPath =
|
|
1332
|
-
return
|
|
1439
|
+
const fullPath = path3__namespace.default.isAbsolute(filePath) ? filePath : path3__namespace.default.join(this.baseDir, filePath);
|
|
1440
|
+
return fs3__namespace.default.readFile(fullPath, "utf-8");
|
|
1333
1441
|
}
|
|
1334
1442
|
/**
|
|
1335
1443
|
* Write content to file
|
|
1336
1444
|
*/
|
|
1337
1445
|
async write(filePath, content) {
|
|
1338
|
-
const fullPath =
|
|
1339
|
-
await
|
|
1340
|
-
await
|
|
1446
|
+
const fullPath = path3__namespace.default.isAbsolute(filePath) ? filePath : path3__namespace.default.join(this.baseDir, filePath);
|
|
1447
|
+
await fs3__namespace.default.mkdir(path3__namespace.default.dirname(fullPath), { recursive: true });
|
|
1448
|
+
await fs3__namespace.default.writeFile(fullPath, content);
|
|
1341
1449
|
}
|
|
1342
1450
|
/**
|
|
1343
1451
|
* Delete a file
|
|
1344
1452
|
*/
|
|
1345
1453
|
async delete(filePath) {
|
|
1346
|
-
const fullPath =
|
|
1454
|
+
const fullPath = path3__namespace.default.isAbsolute(filePath) ? filePath : path3__namespace.default.join(this.baseDir, filePath);
|
|
1347
1455
|
try {
|
|
1348
|
-
await
|
|
1456
|
+
await fs3__namespace.default.unlink(fullPath);
|
|
1349
1457
|
return true;
|
|
1350
1458
|
} catch {
|
|
1351
1459
|
return false;
|
|
@@ -1355,10 +1463,10 @@ var LocalStorage = class {
|
|
|
1355
1463
|
* List files in a directory
|
|
1356
1464
|
*/
|
|
1357
1465
|
async list(dirPath) {
|
|
1358
|
-
const fullPath =
|
|
1466
|
+
const fullPath = path3__namespace.default.isAbsolute(dirPath) ? dirPath : path3__namespace.default.join(this.baseDir, dirPath);
|
|
1359
1467
|
try {
|
|
1360
|
-
const entries = await
|
|
1361
|
-
return entries.filter((e) => e.isFile()).map((e) =>
|
|
1468
|
+
const entries = await fs3__namespace.default.readdir(fullPath, { withFileTypes: true });
|
|
1469
|
+
return entries.filter((e) => e.isFile()).map((e) => path3__namespace.default.join(dirPath, e.name));
|
|
1362
1470
|
} catch {
|
|
1363
1471
|
return [];
|
|
1364
1472
|
}
|
|
@@ -1704,6 +1812,484 @@ function createHttpStorage(options) {
|
|
|
1704
1812
|
return new HttpStorage(options);
|
|
1705
1813
|
}
|
|
1706
1814
|
|
|
1815
|
+
// src/file-integration/source-resolver.ts
|
|
1816
|
+
var FileSourceResolver = class {
|
|
1817
|
+
constructor(config) {
|
|
1818
|
+
this.config = config;
|
|
1819
|
+
this.initializeSources();
|
|
1820
|
+
}
|
|
1821
|
+
sources = /* @__PURE__ */ new Map();
|
|
1822
|
+
/**
|
|
1823
|
+
* Initialize sources from config
|
|
1824
|
+
*/
|
|
1825
|
+
initializeSources() {
|
|
1826
|
+
if (!this.config.file?.sources) {
|
|
1827
|
+
return;
|
|
1828
|
+
}
|
|
1829
|
+
for (const [name, sourceConfig] of Object.entries(this.config.file.sources)) {
|
|
1830
|
+
const resolved = {
|
|
1831
|
+
name,
|
|
1832
|
+
type: "file-plugin",
|
|
1833
|
+
localPath: sourceConfig.local.base_path,
|
|
1834
|
+
isCurrentProject: true,
|
|
1835
|
+
config: sourceConfig
|
|
1836
|
+
};
|
|
1837
|
+
if (sourceConfig.bucket && sourceConfig.type !== "local") {
|
|
1838
|
+
resolved.bucket = sourceConfig.bucket;
|
|
1839
|
+
resolved.prefix = sourceConfig.prefix;
|
|
1840
|
+
resolved.remotePath = this.buildRemotePath(sourceConfig);
|
|
1841
|
+
}
|
|
1842
|
+
this.sources.set(name, resolved);
|
|
1843
|
+
}
|
|
1844
|
+
}
|
|
1845
|
+
/**
|
|
1846
|
+
* Build remote path from source config
|
|
1847
|
+
*/
|
|
1848
|
+
buildRemotePath(source) {
|
|
1849
|
+
const protocol = source.type === "s3" ? "s3://" : source.type === "r2" ? "r2://" : "gcs://";
|
|
1850
|
+
const bucket = source.bucket;
|
|
1851
|
+
const prefix = source.prefix ? `/${source.prefix}` : "";
|
|
1852
|
+
return `${protocol}${bucket}${prefix}`;
|
|
1853
|
+
}
|
|
1854
|
+
/**
|
|
1855
|
+
* Get all available file plugin sources
|
|
1856
|
+
*
|
|
1857
|
+
* @returns Array of resolved file sources
|
|
1858
|
+
*/
|
|
1859
|
+
getAvailableSources() {
|
|
1860
|
+
return Array.from(this.sources.values());
|
|
1861
|
+
}
|
|
1862
|
+
/**
|
|
1863
|
+
* Resolve a source by name
|
|
1864
|
+
*
|
|
1865
|
+
* @param name - Source name (e.g., "specs", "logs")
|
|
1866
|
+
* @returns Resolved source or null if not found
|
|
1867
|
+
*/
|
|
1868
|
+
resolveSource(name) {
|
|
1869
|
+
return this.sources.get(name) || null;
|
|
1870
|
+
}
|
|
1871
|
+
/**
|
|
1872
|
+
* Check if a path belongs to any file plugin source
|
|
1873
|
+
*
|
|
1874
|
+
* @param path - File path to check
|
|
1875
|
+
* @returns True if path matches any source's base_path
|
|
1876
|
+
*/
|
|
1877
|
+
isFilePluginPath(path7) {
|
|
1878
|
+
return this.getSourceForPath(path7) !== null;
|
|
1879
|
+
}
|
|
1880
|
+
/**
|
|
1881
|
+
* Get the source for a given path
|
|
1882
|
+
*
|
|
1883
|
+
* Matches path against all source base_paths and returns the matching source.
|
|
1884
|
+
* Uses longest-match strategy if multiple sources match.
|
|
1885
|
+
*
|
|
1886
|
+
* @param path - File path to match
|
|
1887
|
+
* @returns Matching source or null
|
|
1888
|
+
*/
|
|
1889
|
+
getSourceForPath(path7) {
|
|
1890
|
+
let bestMatch = null;
|
|
1891
|
+
let bestMatchLength = 0;
|
|
1892
|
+
for (const source of this.sources.values()) {
|
|
1893
|
+
const normalizedPath = this.normalizePath(path7);
|
|
1894
|
+
const normalizedBasePath = this.normalizePath(source.localPath);
|
|
1895
|
+
if (normalizedPath.startsWith(normalizedBasePath)) {
|
|
1896
|
+
const matchLength = normalizedBasePath.length;
|
|
1897
|
+
if (matchLength > bestMatchLength) {
|
|
1898
|
+
bestMatch = source;
|
|
1899
|
+
bestMatchLength = matchLength;
|
|
1900
|
+
}
|
|
1901
|
+
}
|
|
1902
|
+
}
|
|
1903
|
+
return bestMatch;
|
|
1904
|
+
}
|
|
1905
|
+
/**
|
|
1906
|
+
* Normalize path for comparison
|
|
1907
|
+
* - Remove leading "./" or "/"
|
|
1908
|
+
* - Remove trailing "/"
|
|
1909
|
+
* - Convert to lowercase for case-insensitive comparison
|
|
1910
|
+
*/
|
|
1911
|
+
normalizePath(path7) {
|
|
1912
|
+
return path7.replace(/^\.\//, "").replace(/^\//, "").replace(/\/$/, "").toLowerCase();
|
|
1913
|
+
}
|
|
1914
|
+
/**
|
|
1915
|
+
* Get source names
|
|
1916
|
+
*
|
|
1917
|
+
* @returns Array of source names
|
|
1918
|
+
*/
|
|
1919
|
+
getSourceNames() {
|
|
1920
|
+
return Array.from(this.sources.keys());
|
|
1921
|
+
}
|
|
1922
|
+
/**
|
|
1923
|
+
* Check if sources are configured
|
|
1924
|
+
*
|
|
1925
|
+
* @returns True if any file plugin sources are configured
|
|
1926
|
+
*/
|
|
1927
|
+
hasSources() {
|
|
1928
|
+
return this.sources.size > 0;
|
|
1929
|
+
}
|
|
1930
|
+
};
|
|
1931
|
+
|
|
1932
|
+
// src/storage/errors.ts
|
|
1933
|
+
var FilePluginFileNotFoundError = class _FilePluginFileNotFoundError extends Error {
|
|
1934
|
+
constructor(filePath, sourceName, options) {
|
|
1935
|
+
const includeCloudSuggestions = options?.includeCloudSuggestions !== false;
|
|
1936
|
+
const storageType = options?.storageType;
|
|
1937
|
+
let message = `File not found: ${filePath}
|
|
1938
|
+
|
|
1939
|
+
`;
|
|
1940
|
+
if (includeCloudSuggestions) {
|
|
1941
|
+
if (storageType) {
|
|
1942
|
+
message += `This file may be in cloud storage (${storageType}).
|
|
1943
|
+
|
|
1944
|
+
`;
|
|
1945
|
+
} else {
|
|
1946
|
+
message += `This file may not have been synced from remote storage yet.
|
|
1947
|
+
`;
|
|
1948
|
+
}
|
|
1949
|
+
message += `To fetch from cloud storage, run:
|
|
1950
|
+
`;
|
|
1951
|
+
message += ` file pull ${sourceName}
|
|
1952
|
+
|
|
1953
|
+
`;
|
|
1954
|
+
message += `Or sync all sources:
|
|
1955
|
+
`;
|
|
1956
|
+
message += ` file sync`;
|
|
1957
|
+
} else {
|
|
1958
|
+
message += `Please ensure the file exists locally or pull it from cloud storage.`;
|
|
1959
|
+
}
|
|
1960
|
+
super(message);
|
|
1961
|
+
this.filePath = filePath;
|
|
1962
|
+
this.sourceName = sourceName;
|
|
1963
|
+
this.name = "FilePluginFileNotFoundError";
|
|
1964
|
+
if (Error.captureStackTrace) {
|
|
1965
|
+
Error.captureStackTrace(this, _FilePluginFileNotFoundError);
|
|
1966
|
+
}
|
|
1967
|
+
}
|
|
1968
|
+
};
|
|
1969
|
+
|
|
1970
|
+
// src/storage/file-plugin.ts
|
|
1971
|
+
var FilePluginStorage = class {
|
|
1972
|
+
constructor(options) {
|
|
1973
|
+
this.options = options;
|
|
1974
|
+
this.sourceResolver = new FileSourceResolver(options.config);
|
|
1975
|
+
this.baseDir = options.baseDir || process.cwd();
|
|
1976
|
+
}
|
|
1977
|
+
name = "file-plugin";
|
|
1978
|
+
type = "local";
|
|
1979
|
+
// Reuse local type for compatibility
|
|
1980
|
+
sourceResolver;
|
|
1981
|
+
baseDir;
|
|
1982
|
+
/**
|
|
1983
|
+
* Check if this provider can handle the reference
|
|
1984
|
+
*
|
|
1985
|
+
* Only handles:
|
|
1986
|
+
* - Current project references
|
|
1987
|
+
* - With sourceType === 'file-plugin'
|
|
1988
|
+
*
|
|
1989
|
+
* @param reference - Resolved reference
|
|
1990
|
+
* @returns True if this provider can handle the reference
|
|
1991
|
+
*/
|
|
1992
|
+
canHandle(reference) {
|
|
1993
|
+
return reference.isCurrentProject && reference.sourceType === "file-plugin";
|
|
1994
|
+
}
|
|
1995
|
+
/**
|
|
1996
|
+
* Fetch content for a reference
|
|
1997
|
+
*
|
|
1998
|
+
* Reads from local filesystem based on the resolved local path.
|
|
1999
|
+
* If file not found and S3 fallback is enabled, throws helpful error.
|
|
2000
|
+
*
|
|
2001
|
+
* @param reference - Resolved reference
|
|
2002
|
+
* @param options - Fetch options (unused for local reads)
|
|
2003
|
+
* @returns Fetch result with content
|
|
2004
|
+
*/
|
|
2005
|
+
async fetch(reference, _options) {
|
|
2006
|
+
if (!reference.localPath) {
|
|
2007
|
+
throw new Error(`File plugin reference missing localPath: ${reference.uri}`);
|
|
2008
|
+
}
|
|
2009
|
+
if (!reference.filePluginSource) {
|
|
2010
|
+
throw new Error(`File plugin reference missing source name: ${reference.uri}`);
|
|
2011
|
+
}
|
|
2012
|
+
const absolutePath = path3__namespace.isAbsolute(reference.localPath) ? path3__namespace.resolve(reference.localPath) : path3__namespace.resolve(this.baseDir, reference.localPath);
|
|
2013
|
+
const source = this.sourceResolver.resolveSource(reference.filePluginSource);
|
|
2014
|
+
if (source) {
|
|
2015
|
+
const allowedDir = path3__namespace.resolve(this.baseDir, source.localPath);
|
|
2016
|
+
if (!absolutePath.startsWith(allowedDir + path3__namespace.sep) && absolutePath !== allowedDir) {
|
|
2017
|
+
throw new Error(
|
|
2018
|
+
`Path traversal detected: ${reference.localPath} resolves outside allowed directory ${source.localPath}`
|
|
2019
|
+
);
|
|
2020
|
+
}
|
|
2021
|
+
}
|
|
2022
|
+
try {
|
|
2023
|
+
const content = await fs3__namespace.readFile(absolutePath);
|
|
2024
|
+
const contentType = this.detectContentType(absolutePath);
|
|
2025
|
+
return {
|
|
2026
|
+
content,
|
|
2027
|
+
contentType,
|
|
2028
|
+
size: content.length,
|
|
2029
|
+
source: "file-plugin",
|
|
2030
|
+
metadata: {
|
|
2031
|
+
filePluginSource: reference.filePluginSource,
|
|
2032
|
+
localPath: absolutePath
|
|
2033
|
+
}
|
|
2034
|
+
};
|
|
2035
|
+
} catch (error) {
|
|
2036
|
+
if (error.code === "ENOENT") {
|
|
2037
|
+
const source2 = this.sourceResolver.resolveSource(reference.filePluginSource);
|
|
2038
|
+
throw this.createFileNotFoundError(reference, source2);
|
|
2039
|
+
}
|
|
2040
|
+
throw error;
|
|
2041
|
+
}
|
|
2042
|
+
}
|
|
2043
|
+
/**
|
|
2044
|
+
* Check if a reference exists
|
|
2045
|
+
*
|
|
2046
|
+
* @param reference - Resolved reference
|
|
2047
|
+
* @param options - Fetch options (unused)
|
|
2048
|
+
* @returns True if file exists
|
|
2049
|
+
*/
|
|
2050
|
+
async exists(reference, _options) {
|
|
2051
|
+
if (!reference.localPath) {
|
|
2052
|
+
return false;
|
|
2053
|
+
}
|
|
2054
|
+
const absolutePath = path3__namespace.isAbsolute(reference.localPath) ? path3__namespace.resolve(reference.localPath) : path3__namespace.resolve(this.baseDir, reference.localPath);
|
|
2055
|
+
if (reference.filePluginSource) {
|
|
2056
|
+
const source = this.sourceResolver.resolveSource(reference.filePluginSource);
|
|
2057
|
+
if (source) {
|
|
2058
|
+
const allowedDir = path3__namespace.resolve(this.baseDir, source.localPath);
|
|
2059
|
+
if (!absolutePath.startsWith(allowedDir + path3__namespace.sep) && absolutePath !== allowedDir) {
|
|
2060
|
+
return false;
|
|
2061
|
+
}
|
|
2062
|
+
}
|
|
2063
|
+
}
|
|
2064
|
+
try {
|
|
2065
|
+
await fs3__namespace.access(absolutePath);
|
|
2066
|
+
return true;
|
|
2067
|
+
} catch {
|
|
2068
|
+
return false;
|
|
2069
|
+
}
|
|
2070
|
+
}
|
|
2071
|
+
/**
|
|
2072
|
+
* Detect content type from file extension
|
|
2073
|
+
*/
|
|
2074
|
+
detectContentType(filePath) {
|
|
2075
|
+
const ext = path3__namespace.extname(filePath).toLowerCase();
|
|
2076
|
+
const mimeTypes = {
|
|
2077
|
+
".md": "text/markdown",
|
|
2078
|
+
".txt": "text/plain",
|
|
2079
|
+
".json": "application/json",
|
|
2080
|
+
".yaml": "text/yaml",
|
|
2081
|
+
".yml": "text/yaml",
|
|
2082
|
+
".html": "text/html",
|
|
2083
|
+
".xml": "application/xml",
|
|
2084
|
+
".log": "text/plain",
|
|
2085
|
+
".js": "application/javascript",
|
|
2086
|
+
".ts": "application/typescript",
|
|
2087
|
+
".py": "text/x-python",
|
|
2088
|
+
".sh": "application/x-sh"
|
|
2089
|
+
};
|
|
2090
|
+
return mimeTypes[ext] || "application/octet-stream";
|
|
2091
|
+
}
|
|
2092
|
+
/**
|
|
2093
|
+
* Create a helpful error message when file is not found
|
|
2094
|
+
*/
|
|
2095
|
+
createFileNotFoundError(reference, source) {
|
|
2096
|
+
const includeCloudSuggestions = this.options.enableS3Fallback !== false;
|
|
2097
|
+
const storageType = source?.config.type;
|
|
2098
|
+
return new FilePluginFileNotFoundError(
|
|
2099
|
+
reference.localPath || reference.path || "",
|
|
2100
|
+
reference.filePluginSource || "",
|
|
2101
|
+
{
|
|
2102
|
+
includeCloudSuggestions,
|
|
2103
|
+
storageType
|
|
2104
|
+
}
|
|
2105
|
+
);
|
|
2106
|
+
}
|
|
2107
|
+
};
|
|
2108
|
+
var execFileAsync = util.promisify(child_process.execFile);
|
|
2109
|
+
async function execFileNoThrow(command, args = [], options) {
|
|
2110
|
+
try {
|
|
2111
|
+
const { stdout, stderr } = await execFileAsync(command, args, {
|
|
2112
|
+
...options,
|
|
2113
|
+
maxBuffer: options?.maxBuffer || 1024 * 1024 * 10
|
|
2114
|
+
// 10MB default
|
|
2115
|
+
});
|
|
2116
|
+
return {
|
|
2117
|
+
stdout: stdout || "",
|
|
2118
|
+
stderr: stderr || "",
|
|
2119
|
+
exitCode: 0
|
|
2120
|
+
};
|
|
2121
|
+
} catch (error) {
|
|
2122
|
+
const exitCode = typeof error.exitCode === "number" ? error.exitCode : 1;
|
|
2123
|
+
return {
|
|
2124
|
+
stdout: error.stdout || "",
|
|
2125
|
+
stderr: error.stderr || error.message || "",
|
|
2126
|
+
exitCode
|
|
2127
|
+
};
|
|
2128
|
+
}
|
|
2129
|
+
}
|
|
2130
|
+
|
|
2131
|
+
// src/storage/s3-archive.ts
|
|
2132
|
+
var S3ArchiveStorage = class {
|
|
2133
|
+
name = "s3-archive";
|
|
2134
|
+
type = "s3-archive";
|
|
2135
|
+
projects;
|
|
2136
|
+
fractaryCli;
|
|
2137
|
+
constructor(options = {}) {
|
|
2138
|
+
this.projects = options.projects || {};
|
|
2139
|
+
this.fractaryCli = options.fractaryCli || "fractary";
|
|
2140
|
+
}
|
|
2141
|
+
/**
|
|
2142
|
+
* Check if this provider can handle the reference
|
|
2143
|
+
*
|
|
2144
|
+
* S3 Archive provider handles references that:
|
|
2145
|
+
* 1. Are for the current project (same org/project)
|
|
2146
|
+
* 2. Have archive enabled in config
|
|
2147
|
+
* 3. Match configured patterns (if specified)
|
|
2148
|
+
*/
|
|
2149
|
+
canHandle(reference) {
|
|
2150
|
+
if (!reference.isCurrentProject) {
|
|
2151
|
+
return false;
|
|
2152
|
+
}
|
|
2153
|
+
const projectKey = `${reference.org}/${reference.project}`;
|
|
2154
|
+
const config = this.projects[projectKey];
|
|
2155
|
+
if (!config || !config.enabled) {
|
|
2156
|
+
return false;
|
|
2157
|
+
}
|
|
2158
|
+
if (config.patterns && config.patterns.length > 0) {
|
|
2159
|
+
return this.matchesPatterns(reference.path, config.patterns);
|
|
2160
|
+
}
|
|
2161
|
+
return true;
|
|
2162
|
+
}
|
|
2163
|
+
/**
|
|
2164
|
+
* Fetch content from S3 archive via fractary-file CLI
|
|
2165
|
+
*/
|
|
2166
|
+
async fetch(reference, options) {
|
|
2167
|
+
const opts = mergeFetchOptions(options);
|
|
2168
|
+
const projectKey = `${reference.org}/${reference.project}`;
|
|
2169
|
+
const config = this.projects[projectKey];
|
|
2170
|
+
if (!config) {
|
|
2171
|
+
throw new Error(`No archive config for project: ${projectKey}`);
|
|
2172
|
+
}
|
|
2173
|
+
const archivePath = this.calculateArchivePath(reference, config);
|
|
2174
|
+
try {
|
|
2175
|
+
const result = await execFileNoThrow(
|
|
2176
|
+
this.fractaryCli,
|
|
2177
|
+
[
|
|
2178
|
+
"file",
|
|
2179
|
+
"read",
|
|
2180
|
+
"--remote-path",
|
|
2181
|
+
archivePath,
|
|
2182
|
+
"--handler",
|
|
2183
|
+
config.handler,
|
|
2184
|
+
...config.bucket ? ["--bucket", config.bucket] : []
|
|
2185
|
+
],
|
|
2186
|
+
{
|
|
2187
|
+
timeout: opts.timeout
|
|
2188
|
+
}
|
|
2189
|
+
);
|
|
2190
|
+
if (result.exitCode !== 0) {
|
|
2191
|
+
throw new Error(`fractary-file read failed: ${result.stderr}`);
|
|
2192
|
+
}
|
|
2193
|
+
const content = Buffer.from(result.stdout);
|
|
2194
|
+
return {
|
|
2195
|
+
content,
|
|
2196
|
+
contentType: detectContentType(reference.path),
|
|
2197
|
+
size: content.length,
|
|
2198
|
+
source: "s3-archive",
|
|
2199
|
+
metadata: {
|
|
2200
|
+
archivePath,
|
|
2201
|
+
bucket: config.bucket,
|
|
2202
|
+
handler: config.handler
|
|
2203
|
+
}
|
|
2204
|
+
};
|
|
2205
|
+
} catch (error) {
|
|
2206
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
2207
|
+
throw new Error(`Failed to fetch from archive: ${message}`);
|
|
2208
|
+
}
|
|
2209
|
+
}
|
|
2210
|
+
/**
|
|
2211
|
+
* Check if archived file exists
|
|
2212
|
+
*
|
|
2213
|
+
* Note: This currently downloads the file to check existence.
|
|
2214
|
+
* TODO: Optimize by using fractary-file 'stat' or 'head' command when available
|
|
2215
|
+
* to avoid downloading full file for existence checks.
|
|
2216
|
+
*/
|
|
2217
|
+
async exists(reference, options) {
|
|
2218
|
+
const projectKey = `${reference.org}/${reference.project}`;
|
|
2219
|
+
const config = this.projects[projectKey];
|
|
2220
|
+
if (!config) {
|
|
2221
|
+
return false;
|
|
2222
|
+
}
|
|
2223
|
+
try {
|
|
2224
|
+
await this.fetch(reference, { ...options, timeout: 5e3 });
|
|
2225
|
+
return true;
|
|
2226
|
+
} catch {
|
|
2227
|
+
return false;
|
|
2228
|
+
}
|
|
2229
|
+
}
|
|
2230
|
+
/**
|
|
2231
|
+
* Calculate archive path from reference
|
|
2232
|
+
*
|
|
2233
|
+
* Pattern: {prefix}/{type}/{org}/{project}/{original-path}
|
|
2234
|
+
*
|
|
2235
|
+
* Examples (with default prefix "archive/"):
|
|
2236
|
+
* specs/WORK-123.md → archive/specs/org/project/specs/WORK-123.md
|
|
2237
|
+
* docs/api.md → archive/docs/org/project/docs/api.md
|
|
2238
|
+
*
|
|
2239
|
+
* Examples (with custom prefix "archived-docs/"):
|
|
2240
|
+
* specs/WORK-123.md → archived-docs/specs/org/project/specs/WORK-123.md
|
|
2241
|
+
*/
|
|
2242
|
+
calculateArchivePath(reference, config) {
|
|
2243
|
+
const type = this.detectType(reference.path);
|
|
2244
|
+
const prefix = config.prefix || "archive/";
|
|
2245
|
+
const trimmedPrefix = prefix.trim();
|
|
2246
|
+
if (!trimmedPrefix) {
|
|
2247
|
+
throw new Error("Archive prefix cannot be empty or whitespace-only");
|
|
2248
|
+
}
|
|
2249
|
+
const normalizedPrefix = trimmedPrefix.endsWith("/") ? trimmedPrefix : `${trimmedPrefix}/`;
|
|
2250
|
+
return `${normalizedPrefix}${type}/${reference.org}/${reference.project}/${reference.path}`;
|
|
2251
|
+
}
|
|
2252
|
+
/**
|
|
2253
|
+
* Detect artifact type from path
|
|
2254
|
+
*
|
|
2255
|
+
* Used to organize archives by type
|
|
2256
|
+
*/
|
|
2257
|
+
detectType(path7) {
|
|
2258
|
+
if (path7.startsWith("specs/")) return "specs";
|
|
2259
|
+
if (path7.startsWith("docs/")) return "docs";
|
|
2260
|
+
if (path7.includes("/logs/")) return "logs";
|
|
2261
|
+
return "misc";
|
|
2262
|
+
}
|
|
2263
|
+
/**
|
|
2264
|
+
* Check if path matches any of the patterns
|
|
2265
|
+
*
|
|
2266
|
+
* Supports glob-style patterns:
|
|
2267
|
+
* - specs/** (all files in specs/)
|
|
2268
|
+
* - *.md (all markdown files)
|
|
2269
|
+
* - docs/*.md (markdown files in docs/)
|
|
2270
|
+
*/
|
|
2271
|
+
matchesPatterns(path7, patterns) {
|
|
2272
|
+
for (const pattern of patterns) {
|
|
2273
|
+
if (this.matchesPattern(path7, pattern)) {
|
|
2274
|
+
return true;
|
|
2275
|
+
}
|
|
2276
|
+
}
|
|
2277
|
+
return false;
|
|
2278
|
+
}
|
|
2279
|
+
/**
|
|
2280
|
+
* Check if path matches a single pattern
|
|
2281
|
+
*/
|
|
2282
|
+
matchesPattern(path7, pattern) {
|
|
2283
|
+
const DOUBLE_STAR = "\0DOUBLE_STAR\0";
|
|
2284
|
+
let regexPattern = pattern.replace(/\*\*/g, DOUBLE_STAR);
|
|
2285
|
+
regexPattern = regexPattern.replace(/[.[\](){}+^$|\\]/g, "\\$&");
|
|
2286
|
+
regexPattern = regexPattern.replace(/\*/g, "[^/]*").replace(/\?/g, "[^/]");
|
|
2287
|
+
regexPattern = regexPattern.replace(new RegExp(DOUBLE_STAR, "g"), ".*");
|
|
2288
|
+
const regex = new RegExp(`^${regexPattern}$`);
|
|
2289
|
+
return regex.test(path7);
|
|
2290
|
+
}
|
|
2291
|
+
};
|
|
2292
|
+
|
|
1707
2293
|
// src/storage/manager.ts
|
|
1708
2294
|
var StorageManager = class {
|
|
1709
2295
|
providers = /* @__PURE__ */ new Map();
|
|
@@ -1712,7 +2298,13 @@ var StorageManager = class {
|
|
|
1712
2298
|
this.providers.set("local", new LocalStorage(config.local));
|
|
1713
2299
|
this.providers.set("github", new GitHubStorage(config.github));
|
|
1714
2300
|
this.providers.set("http", new HttpStorage(config.http));
|
|
1715
|
-
|
|
2301
|
+
if (config.s3Archive) {
|
|
2302
|
+
this.providers.set("s3-archive", new S3ArchiveStorage(config.s3Archive));
|
|
2303
|
+
}
|
|
2304
|
+
if (config.filePlugin) {
|
|
2305
|
+
this.providers.set("file-plugin", new FilePluginStorage(config.filePlugin));
|
|
2306
|
+
}
|
|
2307
|
+
this.priority = config.priority || (config.filePlugin && config.s3Archive ? ["file-plugin", "local", "s3-archive", "github", "http"] : config.filePlugin ? ["file-plugin", "local", "github", "http"] : config.s3Archive ? ["local", "s3-archive", "github", "http"] : ["local", "github", "http"]);
|
|
1716
2308
|
}
|
|
1717
2309
|
/**
|
|
1718
2310
|
* Register a custom storage provider
|
|
@@ -1952,7 +2544,7 @@ var CachePersistence = class {
|
|
|
1952
2544
|
}
|
|
1953
2545
|
const [, org, project, filePath] = match;
|
|
1954
2546
|
const relativePath = filePath || "index";
|
|
1955
|
-
return
|
|
2547
|
+
return path3__namespace.default.join(this.cacheDir, org, project, relativePath + this.extension);
|
|
1956
2548
|
}
|
|
1957
2549
|
/**
|
|
1958
2550
|
* Get the metadata file path for a URI
|
|
@@ -1968,8 +2560,8 @@ var CachePersistence = class {
|
|
|
1968
2560
|
const metadataPath = this.getMetadataPath(uri);
|
|
1969
2561
|
try {
|
|
1970
2562
|
const [metadataJson, content] = await Promise.all([
|
|
1971
|
-
|
|
1972
|
-
|
|
2563
|
+
fs3__namespace.default.readFile(metadataPath, "utf-8"),
|
|
2564
|
+
fs3__namespace.default.readFile(cachePath)
|
|
1973
2565
|
]);
|
|
1974
2566
|
const metadata = JSON.parse(metadataJson);
|
|
1975
2567
|
return {
|
|
@@ -1989,32 +2581,32 @@ var CachePersistence = class {
|
|
|
1989
2581
|
async write(entry) {
|
|
1990
2582
|
const cachePath = this.getCachePath(entry.metadata.uri);
|
|
1991
2583
|
const metadataPath = this.getMetadataPath(entry.metadata.uri);
|
|
1992
|
-
await
|
|
2584
|
+
await fs3__namespace.default.mkdir(path3__namespace.default.dirname(cachePath), { recursive: true });
|
|
1993
2585
|
if (this.atomicWrites) {
|
|
1994
2586
|
const tempCachePath = cachePath + ".tmp";
|
|
1995
2587
|
const tempMetadataPath = metadataPath + ".tmp";
|
|
1996
2588
|
try {
|
|
1997
2589
|
await Promise.all([
|
|
1998
|
-
|
|
1999
|
-
|
|
2590
|
+
fs3__namespace.default.writeFile(tempCachePath, entry.content),
|
|
2591
|
+
fs3__namespace.default.writeFile(tempMetadataPath, JSON.stringify(entry.metadata, null, 2))
|
|
2000
2592
|
]);
|
|
2001
2593
|
await Promise.all([
|
|
2002
|
-
|
|
2003
|
-
|
|
2594
|
+
fs3__namespace.default.rename(tempCachePath, cachePath),
|
|
2595
|
+
fs3__namespace.default.rename(tempMetadataPath, metadataPath)
|
|
2004
2596
|
]);
|
|
2005
2597
|
} catch (error) {
|
|
2006
2598
|
await Promise.all([
|
|
2007
|
-
|
|
2599
|
+
fs3__namespace.default.unlink(tempCachePath).catch(() => {
|
|
2008
2600
|
}),
|
|
2009
|
-
|
|
2601
|
+
fs3__namespace.default.unlink(tempMetadataPath).catch(() => {
|
|
2010
2602
|
})
|
|
2011
2603
|
]);
|
|
2012
2604
|
throw error;
|
|
2013
2605
|
}
|
|
2014
2606
|
} else {
|
|
2015
2607
|
await Promise.all([
|
|
2016
|
-
|
|
2017
|
-
|
|
2608
|
+
fs3__namespace.default.writeFile(cachePath, entry.content),
|
|
2609
|
+
fs3__namespace.default.writeFile(metadataPath, JSON.stringify(entry.metadata, null, 2))
|
|
2018
2610
|
]);
|
|
2019
2611
|
}
|
|
2020
2612
|
}
|
|
@@ -2025,7 +2617,7 @@ var CachePersistence = class {
|
|
|
2025
2617
|
const cachePath = this.getCachePath(uri);
|
|
2026
2618
|
const metadataPath = this.getMetadataPath(uri);
|
|
2027
2619
|
try {
|
|
2028
|
-
await Promise.all([
|
|
2620
|
+
await Promise.all([fs3__namespace.default.unlink(cachePath), fs3__namespace.default.unlink(metadataPath)]);
|
|
2029
2621
|
return true;
|
|
2030
2622
|
} catch (error) {
|
|
2031
2623
|
if (error.code === "ENOENT") {
|
|
@@ -2040,7 +2632,7 @@ var CachePersistence = class {
|
|
|
2040
2632
|
async exists(uri) {
|
|
2041
2633
|
const cachePath = this.getCachePath(uri);
|
|
2042
2634
|
try {
|
|
2043
|
-
await
|
|
2635
|
+
await fs3__namespace.default.access(cachePath);
|
|
2044
2636
|
return true;
|
|
2045
2637
|
} catch {
|
|
2046
2638
|
return false;
|
|
@@ -2052,20 +2644,20 @@ var CachePersistence = class {
|
|
|
2052
2644
|
async list() {
|
|
2053
2645
|
const uris = [];
|
|
2054
2646
|
try {
|
|
2055
|
-
const orgs = await
|
|
2647
|
+
const orgs = await fs3__namespace.default.readdir(this.cacheDir);
|
|
2056
2648
|
for (const org of orgs) {
|
|
2057
|
-
const orgPath =
|
|
2058
|
-
const orgStat = await
|
|
2649
|
+
const orgPath = path3__namespace.default.join(this.cacheDir, org);
|
|
2650
|
+
const orgStat = await fs3__namespace.default.stat(orgPath);
|
|
2059
2651
|
if (!orgStat.isDirectory()) continue;
|
|
2060
|
-
const projects = await
|
|
2652
|
+
const projects = await fs3__namespace.default.readdir(orgPath);
|
|
2061
2653
|
for (const project of projects) {
|
|
2062
|
-
const projectPath =
|
|
2063
|
-
const projectStat = await
|
|
2654
|
+
const projectPath = path3__namespace.default.join(orgPath, project);
|
|
2655
|
+
const projectStat = await fs3__namespace.default.stat(projectPath);
|
|
2064
2656
|
if (!projectStat.isDirectory()) continue;
|
|
2065
2657
|
const files = await this.listFilesRecursive(projectPath);
|
|
2066
2658
|
for (const file of files) {
|
|
2067
2659
|
if (file.endsWith(this.extension)) {
|
|
2068
|
-
const relativePath =
|
|
2660
|
+
const relativePath = path3__namespace.default.relative(projectPath, file);
|
|
2069
2661
|
const filePath = relativePath.slice(0, -this.extension.length);
|
|
2070
2662
|
uris.push(`codex://${org}/${project}/${filePath}`);
|
|
2071
2663
|
}
|
|
@@ -2085,9 +2677,9 @@ var CachePersistence = class {
|
|
|
2085
2677
|
*/
|
|
2086
2678
|
async listFilesRecursive(dir) {
|
|
2087
2679
|
const files = [];
|
|
2088
|
-
const entries = await
|
|
2680
|
+
const entries = await fs3__namespace.default.readdir(dir, { withFileTypes: true });
|
|
2089
2681
|
for (const entry of entries) {
|
|
2090
|
-
const fullPath =
|
|
2682
|
+
const fullPath = path3__namespace.default.join(dir, entry.name);
|
|
2091
2683
|
if (entry.isDirectory()) {
|
|
2092
2684
|
files.push(...await this.listFilesRecursive(fullPath));
|
|
2093
2685
|
} else if (entry.isFile()) {
|
|
@@ -2102,12 +2694,12 @@ var CachePersistence = class {
|
|
|
2102
2694
|
async clear() {
|
|
2103
2695
|
let count = 0;
|
|
2104
2696
|
try {
|
|
2105
|
-
const orgs = await
|
|
2697
|
+
const orgs = await fs3__namespace.default.readdir(this.cacheDir);
|
|
2106
2698
|
for (const org of orgs) {
|
|
2107
|
-
const orgPath =
|
|
2108
|
-
const stat = await
|
|
2699
|
+
const orgPath = path3__namespace.default.join(this.cacheDir, org);
|
|
2700
|
+
const stat = await fs3__namespace.default.stat(orgPath);
|
|
2109
2701
|
if (stat.isDirectory()) {
|
|
2110
|
-
await
|
|
2702
|
+
await fs3__namespace.default.rm(orgPath, { recursive: true });
|
|
2111
2703
|
count++;
|
|
2112
2704
|
}
|
|
2113
2705
|
}
|
|
@@ -2168,7 +2760,7 @@ var CachePersistence = class {
|
|
|
2168
2760
|
* Ensure cache directory exists
|
|
2169
2761
|
*/
|
|
2170
2762
|
async ensureDir() {
|
|
2171
|
-
await
|
|
2763
|
+
await fs3__namespace.default.mkdir(this.cacheDir, { recursive: true });
|
|
2172
2764
|
}
|
|
2173
2765
|
/**
|
|
2174
2766
|
* Get cache directory path
|
|
@@ -2216,8 +2808,17 @@ var CacheManager = class {
|
|
|
2216
2808
|
* Get content for a reference
|
|
2217
2809
|
*
|
|
2218
2810
|
* Implements cache-first strategy with stale-while-revalidate.
|
|
2811
|
+
*
|
|
2812
|
+
* EXCEPTION: File plugin sources (current project files) bypass cache entirely.
|
|
2813
|
+
* They are always read fresh from disk for optimal development experience.
|
|
2219
2814
|
*/
|
|
2220
2815
|
async get(reference, options) {
|
|
2816
|
+
if (reference.isCurrentProject && reference.sourceType === "file-plugin") {
|
|
2817
|
+
if (!this.storage) {
|
|
2818
|
+
throw new Error("Storage manager not set");
|
|
2819
|
+
}
|
|
2820
|
+
return await this.storage.fetch(reference, options);
|
|
2821
|
+
}
|
|
2221
2822
|
const ttl = options?.ttl ?? this.config.defaultTtl;
|
|
2222
2823
|
let entry = this.memoryCache.get(reference.uri);
|
|
2223
2824
|
if (!entry && this.persistence) {
|
|
@@ -2423,13 +3024,18 @@ var CacheManager = class {
|
|
|
2423
3024
|
}
|
|
2424
3025
|
/**
|
|
2425
3026
|
* Fetch content and store in cache
|
|
3027
|
+
*
|
|
3028
|
+
* EXCEPTION: File plugin sources are not cached (should not reach here,
|
|
3029
|
+
* but added as safety check).
|
|
2426
3030
|
*/
|
|
2427
3031
|
async fetchAndCache(reference, ttl, options) {
|
|
2428
3032
|
if (!this.storage) {
|
|
2429
3033
|
throw new Error("Storage manager not set");
|
|
2430
3034
|
}
|
|
2431
3035
|
const result = await this.storage.fetch(reference, options);
|
|
2432
|
-
|
|
3036
|
+
if (!(reference.isCurrentProject && reference.sourceType === "file-plugin")) {
|
|
3037
|
+
await this.set(reference.uri, result, ttl);
|
|
3038
|
+
}
|
|
2433
3039
|
return result;
|
|
2434
3040
|
}
|
|
2435
3041
|
/**
|
|
@@ -2548,11 +3154,11 @@ var DEFAULT_SYNC_CONFIG = {
|
|
|
2548
3154
|
deleteOrphans: false,
|
|
2549
3155
|
conflictStrategy: "newest"
|
|
2550
3156
|
};
|
|
2551
|
-
function evaluatePath(
|
|
3157
|
+
function evaluatePath(path7, rules, direction, defaultExcludes = []) {
|
|
2552
3158
|
for (const pattern of defaultExcludes) {
|
|
2553
|
-
if (micromatch3__default.default.isMatch(
|
|
3159
|
+
if (micromatch3__default.default.isMatch(path7, pattern)) {
|
|
2554
3160
|
return {
|
|
2555
|
-
path:
|
|
3161
|
+
path: path7,
|
|
2556
3162
|
shouldSync: false,
|
|
2557
3163
|
reason: `Excluded by default pattern: ${pattern}`
|
|
2558
3164
|
};
|
|
@@ -2563,9 +3169,9 @@ function evaluatePath(path6, rules, direction, defaultExcludes = []) {
|
|
|
2563
3169
|
if (rule.direction && rule.direction !== direction) {
|
|
2564
3170
|
continue;
|
|
2565
3171
|
}
|
|
2566
|
-
if (micromatch3__default.default.isMatch(
|
|
3172
|
+
if (micromatch3__default.default.isMatch(path7, rule.pattern)) {
|
|
2567
3173
|
return {
|
|
2568
|
-
path:
|
|
3174
|
+
path: path7,
|
|
2569
3175
|
shouldSync: rule.include,
|
|
2570
3176
|
matchedRule: rule,
|
|
2571
3177
|
reason: rule.include ? `Included by rule: ${rule.pattern}` : `Excluded by rule: ${rule.pattern}`
|
|
@@ -2573,21 +3179,21 @@ function evaluatePath(path6, rules, direction, defaultExcludes = []) {
|
|
|
2573
3179
|
}
|
|
2574
3180
|
}
|
|
2575
3181
|
return {
|
|
2576
|
-
path:
|
|
3182
|
+
path: path7,
|
|
2577
3183
|
shouldSync: true,
|
|
2578
3184
|
reason: "No matching rule, included by default"
|
|
2579
3185
|
};
|
|
2580
3186
|
}
|
|
2581
3187
|
function evaluatePaths(paths, rules, direction, defaultExcludes = []) {
|
|
2582
3188
|
const results = /* @__PURE__ */ new Map();
|
|
2583
|
-
for (const
|
|
2584
|
-
results.set(
|
|
3189
|
+
for (const path7 of paths) {
|
|
3190
|
+
results.set(path7, evaluatePath(path7, rules, direction, defaultExcludes));
|
|
2585
3191
|
}
|
|
2586
3192
|
return results;
|
|
2587
3193
|
}
|
|
2588
3194
|
function filterSyncablePaths(paths, rules, direction, defaultExcludes = []) {
|
|
2589
3195
|
return paths.filter(
|
|
2590
|
-
(
|
|
3196
|
+
(path7) => evaluatePath(path7, rules, direction, defaultExcludes).shouldSync
|
|
2591
3197
|
);
|
|
2592
3198
|
}
|
|
2593
3199
|
function createRulesFromPatterns(include = [], exclude = []) {
|
|
@@ -2853,8 +3459,8 @@ async function scanCodexWithRouting(options) {
|
|
|
2853
3459
|
for (const filePath of allFiles) {
|
|
2854
3460
|
totalScanned++;
|
|
2855
3461
|
try {
|
|
2856
|
-
const fullPath =
|
|
2857
|
-
const stats = await
|
|
3462
|
+
const fullPath = path3__namespace.default.join(codexDir, filePath);
|
|
3463
|
+
const stats = await fs3__namespace.default.stat(fullPath);
|
|
2858
3464
|
if (stats.size > maxFileSize) {
|
|
2859
3465
|
totalSkipped++;
|
|
2860
3466
|
errors.push({
|
|
@@ -2950,10 +3556,10 @@ async function listAllFilesRecursive(dirPath) {
|
|
|
2950
3556
|
const files = [];
|
|
2951
3557
|
async function scanDirectory(currentPath, relativePath = "") {
|
|
2952
3558
|
try {
|
|
2953
|
-
const entries = await
|
|
3559
|
+
const entries = await fs3__namespace.default.readdir(currentPath, { withFileTypes: true });
|
|
2954
3560
|
for (const entry of entries) {
|
|
2955
|
-
const entryPath =
|
|
2956
|
-
const entryRelativePath = relativePath ?
|
|
3561
|
+
const entryPath = path3__namespace.default.join(currentPath, entry.name);
|
|
3562
|
+
const entryRelativePath = relativePath ? path3__namespace.default.join(relativePath, entry.name) : entry.name;
|
|
2957
3563
|
if (entry.isDirectory()) {
|
|
2958
3564
|
if (entry.name.startsWith(".") || entry.name === "node_modules" || entry.name === "dist" || entry.name === "build") {
|
|
2959
3565
|
continue;
|
|
@@ -3192,17 +3798,17 @@ var SyncManager = class {
|
|
|
3192
3798
|
if (file.operation === "create" || file.operation === "update") {
|
|
3193
3799
|
const sourcePath = `${plan.source}/${file.path}`;
|
|
3194
3800
|
const targetPath = `${plan.target}/${file.path}`;
|
|
3195
|
-
const
|
|
3196
|
-
const
|
|
3197
|
-
const targetDir =
|
|
3198
|
-
await
|
|
3199
|
-
await
|
|
3801
|
+
const fs5 = await import('fs/promises');
|
|
3802
|
+
const path7 = await import('path');
|
|
3803
|
+
const targetDir = path7.dirname(targetPath);
|
|
3804
|
+
await fs5.mkdir(targetDir, { recursive: true });
|
|
3805
|
+
await fs5.copyFile(sourcePath, targetPath);
|
|
3200
3806
|
synced++;
|
|
3201
3807
|
} else if (file.operation === "delete") {
|
|
3202
3808
|
const targetPath = `${plan.target}/${file.path}`;
|
|
3203
|
-
const
|
|
3809
|
+
const fs5 = await import('fs/promises');
|
|
3204
3810
|
try {
|
|
3205
|
-
await
|
|
3811
|
+
await fs5.unlink(targetPath);
|
|
3206
3812
|
synced++;
|
|
3207
3813
|
} catch (error) {
|
|
3208
3814
|
if (error.code !== "ENOENT") {
|
|
@@ -3257,15 +3863,15 @@ var SyncManager = class {
|
|
|
3257
3863
|
/**
|
|
3258
3864
|
* Get sync status for a file
|
|
3259
3865
|
*/
|
|
3260
|
-
async getFileStatus(
|
|
3866
|
+
async getFileStatus(path7) {
|
|
3261
3867
|
const manifest = await this.loadManifest();
|
|
3262
|
-
return manifest?.entries[
|
|
3868
|
+
return manifest?.entries[path7] ?? null;
|
|
3263
3869
|
}
|
|
3264
3870
|
/**
|
|
3265
3871
|
* Check if a file is synced
|
|
3266
3872
|
*/
|
|
3267
|
-
async isFileSynced(
|
|
3268
|
-
const status = await this.getFileStatus(
|
|
3873
|
+
async isFileSynced(path7) {
|
|
3874
|
+
const status = await this.getFileStatus(path7);
|
|
3269
3875
|
return status !== null;
|
|
3270
3876
|
}
|
|
3271
3877
|
/**
|
|
@@ -3349,19 +3955,19 @@ function ruleMatchesContext(rule, context) {
|
|
|
3349
3955
|
}
|
|
3350
3956
|
return true;
|
|
3351
3957
|
}
|
|
3352
|
-
function ruleMatchesPath(rule,
|
|
3353
|
-
return micromatch3__default.default.isMatch(
|
|
3958
|
+
function ruleMatchesPath(rule, path7) {
|
|
3959
|
+
return micromatch3__default.default.isMatch(path7, rule.pattern);
|
|
3354
3960
|
}
|
|
3355
3961
|
function ruleMatchesAction(rule, action) {
|
|
3356
3962
|
return rule.actions.includes(action);
|
|
3357
3963
|
}
|
|
3358
|
-
function evaluatePermission(
|
|
3964
|
+
function evaluatePermission(path7, action, context, config) {
|
|
3359
3965
|
const sortedRules = [...config.rules].sort((a, b) => (b.priority ?? 0) - (a.priority ?? 0));
|
|
3360
3966
|
for (const rule of sortedRules) {
|
|
3361
3967
|
if (!ruleMatchesContext(rule, context)) {
|
|
3362
3968
|
continue;
|
|
3363
3969
|
}
|
|
3364
|
-
if (!ruleMatchesPath(rule,
|
|
3970
|
+
if (!ruleMatchesPath(rule, path7)) {
|
|
3365
3971
|
continue;
|
|
3366
3972
|
}
|
|
3367
3973
|
if (!ruleMatchesAction(rule, action)) {
|
|
@@ -3380,24 +3986,24 @@ function evaluatePermission(path6, action, context, config) {
|
|
|
3380
3986
|
reason: config.defaultAllow ? "Allowed by default" : "Denied by default"
|
|
3381
3987
|
};
|
|
3382
3988
|
}
|
|
3383
|
-
function isAllowed(
|
|
3384
|
-
const result = evaluatePermission(
|
|
3989
|
+
function isAllowed(path7, action, context, config) {
|
|
3990
|
+
const result = evaluatePermission(path7, action, context, config);
|
|
3385
3991
|
return result.allowed;
|
|
3386
3992
|
}
|
|
3387
|
-
function hasPermission(
|
|
3388
|
-
const result = evaluatePermission(
|
|
3993
|
+
function hasPermission(path7, action, requiredLevel, context, config) {
|
|
3994
|
+
const result = evaluatePermission(path7, action, context, config);
|
|
3389
3995
|
return levelGrants(result.level, requiredLevel);
|
|
3390
3996
|
}
|
|
3391
3997
|
function evaluatePermissions(paths, action, context, config) {
|
|
3392
3998
|
const results = /* @__PURE__ */ new Map();
|
|
3393
|
-
for (const
|
|
3394
|
-
results.set(
|
|
3999
|
+
for (const path7 of paths) {
|
|
4000
|
+
results.set(path7, evaluatePermission(path7, action, context, config));
|
|
3395
4001
|
}
|
|
3396
4002
|
return results;
|
|
3397
4003
|
}
|
|
3398
4004
|
function filterByPermission(paths, action, context, config, requiredLevel = "read") {
|
|
3399
|
-
return paths.filter((
|
|
3400
|
-
const result = evaluatePermission(
|
|
4005
|
+
return paths.filter((path7) => {
|
|
4006
|
+
const result = evaluatePermission(path7, action, context, config);
|
|
3401
4007
|
return levelGrants(result.level, requiredLevel);
|
|
3402
4008
|
});
|
|
3403
4009
|
}
|
|
@@ -3488,21 +4094,21 @@ var PermissionManager = class {
|
|
|
3488
4094
|
/**
|
|
3489
4095
|
* Check if an action is allowed for a path
|
|
3490
4096
|
*/
|
|
3491
|
-
isAllowed(
|
|
3492
|
-
const result = this.evaluate(
|
|
4097
|
+
isAllowed(path7, action, context) {
|
|
4098
|
+
const result = this.evaluate(path7, action, context);
|
|
3493
4099
|
return result.allowed;
|
|
3494
4100
|
}
|
|
3495
4101
|
/**
|
|
3496
4102
|
* Check if a permission level is granted
|
|
3497
4103
|
*/
|
|
3498
|
-
hasPermission(
|
|
3499
|
-
const result = this.evaluate(
|
|
4104
|
+
hasPermission(path7, action, requiredLevel, context) {
|
|
4105
|
+
const result = this.evaluate(path7, action, context);
|
|
3500
4106
|
return levelGrants(result.level, requiredLevel);
|
|
3501
4107
|
}
|
|
3502
4108
|
/**
|
|
3503
4109
|
* Evaluate permission for a path and action
|
|
3504
4110
|
*/
|
|
3505
|
-
evaluate(
|
|
4111
|
+
evaluate(path7, action, context) {
|
|
3506
4112
|
const mergedContext = { ...this.defaultContext, ...context };
|
|
3507
4113
|
if (!this.config.enforced) {
|
|
3508
4114
|
return {
|
|
@@ -3511,7 +4117,7 @@ var PermissionManager = class {
|
|
|
3511
4117
|
reason: "Permissions not enforced"
|
|
3512
4118
|
};
|
|
3513
4119
|
}
|
|
3514
|
-
return evaluatePermission(
|
|
4120
|
+
return evaluatePermission(path7, action, mergedContext, this.config);
|
|
3515
4121
|
}
|
|
3516
4122
|
/**
|
|
3517
4123
|
* Filter paths by permission
|
|
@@ -3614,12 +4220,12 @@ var PermissionManager = class {
|
|
|
3614
4220
|
/**
|
|
3615
4221
|
* Assert permission (throws if denied)
|
|
3616
4222
|
*/
|
|
3617
|
-
assertPermission(
|
|
3618
|
-
const result = this.evaluate(
|
|
4223
|
+
assertPermission(path7, action, requiredLevel = "read", context) {
|
|
4224
|
+
const result = this.evaluate(path7, action, context);
|
|
3619
4225
|
if (!levelGrants(result.level, requiredLevel)) {
|
|
3620
4226
|
throw new PermissionDeniedError(
|
|
3621
|
-
`Permission denied for ${action} on ${
|
|
3622
|
-
|
|
4227
|
+
`Permission denied for ${action} on ${path7}: ${result.reason}`,
|
|
4228
|
+
path7,
|
|
3623
4229
|
action,
|
|
3624
4230
|
result
|
|
3625
4231
|
);
|
|
@@ -3627,9 +4233,9 @@ var PermissionManager = class {
|
|
|
3627
4233
|
}
|
|
3628
4234
|
};
|
|
3629
4235
|
var PermissionDeniedError = class extends Error {
|
|
3630
|
-
constructor(message,
|
|
4236
|
+
constructor(message, path7, action, result) {
|
|
3631
4237
|
super(message);
|
|
3632
|
-
this.path =
|
|
4238
|
+
this.path = path7;
|
|
3633
4239
|
this.action = action;
|
|
3634
4240
|
this.result = result;
|
|
3635
4241
|
this.name = "PermissionDeniedError";
|
|
@@ -4126,8 +4732,8 @@ function convertToUri(reference, options = {}) {
|
|
|
4126
4732
|
const parts = parseReference2(trimmed);
|
|
4127
4733
|
const org = parts.org || options.defaultOrg || "_";
|
|
4128
4734
|
const project = parts.project || options.defaultProject || "_";
|
|
4129
|
-
const
|
|
4130
|
-
return `codex://${org}/${project}/${
|
|
4735
|
+
const path7 = parts.path;
|
|
4736
|
+
return `codex://${org}/${project}/${path7}`;
|
|
4131
4737
|
}
|
|
4132
4738
|
function parseReference2(reference) {
|
|
4133
4739
|
const trimmed = reference.trim();
|
|
@@ -4223,6 +4829,8 @@ exports.DEFAULT_MIGRATION_OPTIONS = DEFAULT_MIGRATION_OPTIONS;
|
|
|
4223
4829
|
exports.DEFAULT_PERMISSION_CONFIG = DEFAULT_PERMISSION_CONFIG;
|
|
4224
4830
|
exports.DEFAULT_SYNC_CONFIG = DEFAULT_SYNC_CONFIG;
|
|
4225
4831
|
exports.DEFAULT_TYPE = DEFAULT_TYPE;
|
|
4832
|
+
exports.FilePluginFileNotFoundError = FilePluginFileNotFoundError;
|
|
4833
|
+
exports.FilePluginStorage = FilePluginStorage;
|
|
4226
4834
|
exports.GitHubStorage = GitHubStorage;
|
|
4227
4835
|
exports.HttpStorage = HttpStorage;
|
|
4228
4836
|
exports.LEGACY_PATTERNS = LEGACY_PATTERNS;
|