@fractary/codex 0.7.0 → 0.8.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 +209 -3
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +69 -1
- package/dist/index.d.ts +69 -1
- package/dist/index.js +210 -4
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -6,6 +6,7 @@ var child_process = require('child_process');
|
|
|
6
6
|
var zod = require('zod');
|
|
7
7
|
var yaml = require('js-yaml');
|
|
8
8
|
var fs2 = require('fs/promises');
|
|
9
|
+
var util = require('util');
|
|
9
10
|
|
|
10
11
|
function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
|
|
11
12
|
|
|
@@ -843,6 +844,16 @@ var DirectionalSyncSchema = zod.z.object({
|
|
|
843
844
|
default_to_codex: zod.z.array(zod.z.string()).optional(),
|
|
844
845
|
default_from_codex: zod.z.array(zod.z.string()).optional()
|
|
845
846
|
});
|
|
847
|
+
var ArchiveProjectConfigSchema = zod.z.object({
|
|
848
|
+
enabled: zod.z.boolean(),
|
|
849
|
+
handler: zod.z.enum(["s3", "r2", "gcs", "local"]),
|
|
850
|
+
bucket: zod.z.string().optional(),
|
|
851
|
+
prefix: zod.z.string().optional(),
|
|
852
|
+
patterns: zod.z.array(zod.z.string()).optional()
|
|
853
|
+
});
|
|
854
|
+
var ArchiveConfigSchema = zod.z.object({
|
|
855
|
+
projects: zod.z.record(ArchiveProjectConfigSchema)
|
|
856
|
+
});
|
|
846
857
|
var CodexConfigSchema = zod.z.object({
|
|
847
858
|
organizationSlug: zod.z.string(),
|
|
848
859
|
directories: zod.z.object({
|
|
@@ -852,7 +863,9 @@ var CodexConfigSchema = zod.z.object({
|
|
|
852
863
|
}).optional(),
|
|
853
864
|
rules: SyncRulesSchema.optional(),
|
|
854
865
|
// Directional sync configuration
|
|
855
|
-
sync: DirectionalSyncSchema.optional()
|
|
866
|
+
sync: DirectionalSyncSchema.optional(),
|
|
867
|
+
// Archive configuration
|
|
868
|
+
archive: ArchiveConfigSchema.optional()
|
|
856
869
|
}).strict();
|
|
857
870
|
function parseMetadata(content, options = {}) {
|
|
858
871
|
const { strict = true, normalize = true } = options;
|
|
@@ -977,11 +990,17 @@ function getDefaultRules() {
|
|
|
977
990
|
defaultExclude: []
|
|
978
991
|
};
|
|
979
992
|
}
|
|
993
|
+
function getDefaultArchiveConfig() {
|
|
994
|
+
return {
|
|
995
|
+
projects: {}
|
|
996
|
+
};
|
|
997
|
+
}
|
|
980
998
|
function getDefaultConfig(orgSlug) {
|
|
981
999
|
return {
|
|
982
1000
|
organizationSlug: orgSlug,
|
|
983
1001
|
directories: getDefaultDirectories(orgSlug),
|
|
984
|
-
rules: getDefaultRules()
|
|
1002
|
+
rules: getDefaultRules(),
|
|
1003
|
+
archive: getDefaultArchiveConfig()
|
|
985
1004
|
};
|
|
986
1005
|
}
|
|
987
1006
|
|
|
@@ -1703,6 +1722,190 @@ var HttpStorage = class {
|
|
|
1703
1722
|
function createHttpStorage(options) {
|
|
1704
1723
|
return new HttpStorage(options);
|
|
1705
1724
|
}
|
|
1725
|
+
var execFileAsync = util.promisify(child_process.execFile);
|
|
1726
|
+
async function execFileNoThrow(command, args = [], options) {
|
|
1727
|
+
try {
|
|
1728
|
+
const { stdout, stderr } = await execFileAsync(command, args, {
|
|
1729
|
+
...options,
|
|
1730
|
+
maxBuffer: options?.maxBuffer || 1024 * 1024 * 10
|
|
1731
|
+
// 10MB default
|
|
1732
|
+
});
|
|
1733
|
+
return {
|
|
1734
|
+
stdout: stdout || "",
|
|
1735
|
+
stderr: stderr || "",
|
|
1736
|
+
exitCode: 0
|
|
1737
|
+
};
|
|
1738
|
+
} catch (error) {
|
|
1739
|
+
const exitCode = typeof error.exitCode === "number" ? error.exitCode : 1;
|
|
1740
|
+
return {
|
|
1741
|
+
stdout: error.stdout || "",
|
|
1742
|
+
stderr: error.stderr || error.message || "",
|
|
1743
|
+
exitCode
|
|
1744
|
+
};
|
|
1745
|
+
}
|
|
1746
|
+
}
|
|
1747
|
+
|
|
1748
|
+
// src/storage/s3-archive.ts
|
|
1749
|
+
var S3ArchiveStorage = class {
|
|
1750
|
+
name = "s3-archive";
|
|
1751
|
+
type = "s3-archive";
|
|
1752
|
+
projects;
|
|
1753
|
+
fractaryCli;
|
|
1754
|
+
constructor(options = {}) {
|
|
1755
|
+
this.projects = options.projects || {};
|
|
1756
|
+
this.fractaryCli = options.fractaryCli || "fractary";
|
|
1757
|
+
}
|
|
1758
|
+
/**
|
|
1759
|
+
* Check if this provider can handle the reference
|
|
1760
|
+
*
|
|
1761
|
+
* S3 Archive provider handles references that:
|
|
1762
|
+
* 1. Are for the current project (same org/project)
|
|
1763
|
+
* 2. Have archive enabled in config
|
|
1764
|
+
* 3. Match configured patterns (if specified)
|
|
1765
|
+
*/
|
|
1766
|
+
canHandle(reference) {
|
|
1767
|
+
if (!reference.isCurrentProject) {
|
|
1768
|
+
return false;
|
|
1769
|
+
}
|
|
1770
|
+
const projectKey = `${reference.org}/${reference.project}`;
|
|
1771
|
+
const config = this.projects[projectKey];
|
|
1772
|
+
if (!config || !config.enabled) {
|
|
1773
|
+
return false;
|
|
1774
|
+
}
|
|
1775
|
+
if (config.patterns && config.patterns.length > 0) {
|
|
1776
|
+
return this.matchesPatterns(reference.path, config.patterns);
|
|
1777
|
+
}
|
|
1778
|
+
return true;
|
|
1779
|
+
}
|
|
1780
|
+
/**
|
|
1781
|
+
* Fetch content from S3 archive via fractary-file CLI
|
|
1782
|
+
*/
|
|
1783
|
+
async fetch(reference, options) {
|
|
1784
|
+
const opts = mergeFetchOptions(options);
|
|
1785
|
+
const projectKey = `${reference.org}/${reference.project}`;
|
|
1786
|
+
const config = this.projects[projectKey];
|
|
1787
|
+
if (!config) {
|
|
1788
|
+
throw new Error(`No archive config for project: ${projectKey}`);
|
|
1789
|
+
}
|
|
1790
|
+
const archivePath = this.calculateArchivePath(reference, config);
|
|
1791
|
+
try {
|
|
1792
|
+
const result = await execFileNoThrow(
|
|
1793
|
+
this.fractaryCli,
|
|
1794
|
+
[
|
|
1795
|
+
"file",
|
|
1796
|
+
"read",
|
|
1797
|
+
"--remote-path",
|
|
1798
|
+
archivePath,
|
|
1799
|
+
"--handler",
|
|
1800
|
+
config.handler,
|
|
1801
|
+
...config.bucket ? ["--bucket", config.bucket] : []
|
|
1802
|
+
],
|
|
1803
|
+
{
|
|
1804
|
+
timeout: opts.timeout
|
|
1805
|
+
}
|
|
1806
|
+
);
|
|
1807
|
+
if (result.exitCode !== 0) {
|
|
1808
|
+
throw new Error(`fractary-file read failed: ${result.stderr}`);
|
|
1809
|
+
}
|
|
1810
|
+
const content = Buffer.from(result.stdout);
|
|
1811
|
+
return {
|
|
1812
|
+
content,
|
|
1813
|
+
contentType: detectContentType(reference.path),
|
|
1814
|
+
size: content.length,
|
|
1815
|
+
source: "s3-archive",
|
|
1816
|
+
metadata: {
|
|
1817
|
+
archivePath,
|
|
1818
|
+
bucket: config.bucket,
|
|
1819
|
+
handler: config.handler
|
|
1820
|
+
}
|
|
1821
|
+
};
|
|
1822
|
+
} catch (error) {
|
|
1823
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1824
|
+
throw new Error(`Failed to fetch from archive: ${message}`);
|
|
1825
|
+
}
|
|
1826
|
+
}
|
|
1827
|
+
/**
|
|
1828
|
+
* Check if archived file exists
|
|
1829
|
+
*
|
|
1830
|
+
* Note: This currently downloads the file to check existence.
|
|
1831
|
+
* TODO: Optimize by using fractary-file 'stat' or 'head' command when available
|
|
1832
|
+
* to avoid downloading full file for existence checks.
|
|
1833
|
+
*/
|
|
1834
|
+
async exists(reference, options) {
|
|
1835
|
+
const projectKey = `${reference.org}/${reference.project}`;
|
|
1836
|
+
const config = this.projects[projectKey];
|
|
1837
|
+
if (!config) {
|
|
1838
|
+
return false;
|
|
1839
|
+
}
|
|
1840
|
+
try {
|
|
1841
|
+
await this.fetch(reference, { ...options, timeout: 5e3 });
|
|
1842
|
+
return true;
|
|
1843
|
+
} catch {
|
|
1844
|
+
return false;
|
|
1845
|
+
}
|
|
1846
|
+
}
|
|
1847
|
+
/**
|
|
1848
|
+
* Calculate archive path from reference
|
|
1849
|
+
*
|
|
1850
|
+
* Pattern: {prefix}/{type}/{org}/{project}/{original-path}
|
|
1851
|
+
*
|
|
1852
|
+
* Examples (with default prefix "archive/"):
|
|
1853
|
+
* specs/WORK-123.md → archive/specs/org/project/specs/WORK-123.md
|
|
1854
|
+
* docs/api.md → archive/docs/org/project/docs/api.md
|
|
1855
|
+
*
|
|
1856
|
+
* Examples (with custom prefix "archived-docs/"):
|
|
1857
|
+
* specs/WORK-123.md → archived-docs/specs/org/project/specs/WORK-123.md
|
|
1858
|
+
*/
|
|
1859
|
+
calculateArchivePath(reference, config) {
|
|
1860
|
+
const type = this.detectType(reference.path);
|
|
1861
|
+
const prefix = config.prefix || "archive/";
|
|
1862
|
+
const trimmedPrefix = prefix.trim();
|
|
1863
|
+
if (!trimmedPrefix) {
|
|
1864
|
+
throw new Error("Archive prefix cannot be empty or whitespace-only");
|
|
1865
|
+
}
|
|
1866
|
+
const normalizedPrefix = trimmedPrefix.endsWith("/") ? trimmedPrefix : `${trimmedPrefix}/`;
|
|
1867
|
+
return `${normalizedPrefix}${type}/${reference.org}/${reference.project}/${reference.path}`;
|
|
1868
|
+
}
|
|
1869
|
+
/**
|
|
1870
|
+
* Detect artifact type from path
|
|
1871
|
+
*
|
|
1872
|
+
* Used to organize archives by type
|
|
1873
|
+
*/
|
|
1874
|
+
detectType(path6) {
|
|
1875
|
+
if (path6.startsWith("specs/")) return "specs";
|
|
1876
|
+
if (path6.startsWith("docs/")) return "docs";
|
|
1877
|
+
if (path6.includes("/logs/")) return "logs";
|
|
1878
|
+
return "misc";
|
|
1879
|
+
}
|
|
1880
|
+
/**
|
|
1881
|
+
* Check if path matches any of the patterns
|
|
1882
|
+
*
|
|
1883
|
+
* Supports glob-style patterns:
|
|
1884
|
+
* - specs/** (all files in specs/)
|
|
1885
|
+
* - *.md (all markdown files)
|
|
1886
|
+
* - docs/*.md (markdown files in docs/)
|
|
1887
|
+
*/
|
|
1888
|
+
matchesPatterns(path6, patterns) {
|
|
1889
|
+
for (const pattern of patterns) {
|
|
1890
|
+
if (this.matchesPattern(path6, pattern)) {
|
|
1891
|
+
return true;
|
|
1892
|
+
}
|
|
1893
|
+
}
|
|
1894
|
+
return false;
|
|
1895
|
+
}
|
|
1896
|
+
/**
|
|
1897
|
+
* Check if path matches a single pattern
|
|
1898
|
+
*/
|
|
1899
|
+
matchesPattern(path6, pattern) {
|
|
1900
|
+
const DOUBLE_STAR = "\0DOUBLE_STAR\0";
|
|
1901
|
+
let regexPattern = pattern.replace(/\*\*/g, DOUBLE_STAR);
|
|
1902
|
+
regexPattern = regexPattern.replace(/[.[\](){}+^$|\\]/g, "\\$&");
|
|
1903
|
+
regexPattern = regexPattern.replace(/\*/g, "[^/]*").replace(/\?/g, "[^/]");
|
|
1904
|
+
regexPattern = regexPattern.replace(new RegExp(DOUBLE_STAR, "g"), ".*");
|
|
1905
|
+
const regex = new RegExp(`^${regexPattern}$`);
|
|
1906
|
+
return regex.test(path6);
|
|
1907
|
+
}
|
|
1908
|
+
};
|
|
1706
1909
|
|
|
1707
1910
|
// src/storage/manager.ts
|
|
1708
1911
|
var StorageManager = class {
|
|
@@ -1712,7 +1915,10 @@ var StorageManager = class {
|
|
|
1712
1915
|
this.providers.set("local", new LocalStorage(config.local));
|
|
1713
1916
|
this.providers.set("github", new GitHubStorage(config.github));
|
|
1714
1917
|
this.providers.set("http", new HttpStorage(config.http));
|
|
1715
|
-
|
|
1918
|
+
if (config.s3Archive) {
|
|
1919
|
+
this.providers.set("s3-archive", new S3ArchiveStorage(config.s3Archive));
|
|
1920
|
+
}
|
|
1921
|
+
this.priority = config.priority || (config.s3Archive ? ["local", "s3-archive", "github", "http"] : ["local", "github", "http"]);
|
|
1716
1922
|
}
|
|
1717
1923
|
/**
|
|
1718
1924
|
* Register a custom storage provider
|