resourcexjs 1.7.0 → 2.0.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/arp.js CHANGED
@@ -8,6 +8,10 @@ import { join as join2 } from "node:path";
8
8
  import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2, rm as rm2, stat as stat2, readdir as readdir2 } from "node:fs/promises";
9
9
  import { gzip as gzip2, gunzip as gunzip2 } from "node:zlib";
10
10
  import { promisify as promisify2 } from "node:util";
11
+ import { homedir as homedir2 } from "node:os";
12
+ import { join as join22 } from "node:path";
13
+ import { readFile as readFile22, stat as stat22, readdir as readdir22, mkdir as mkdir22 } from "node:fs/promises";
14
+ import { execSync } from "node:child_process";
11
15
 
12
16
  class ARPError extends Error {
13
17
  constructor(message, options) {
@@ -283,6 +287,9 @@ var httpTransport = new HttpTransportHandler("http");
283
287
  function isRemoteConfig(config) {
284
288
  return config !== undefined && "endpoint" in config;
285
289
  }
290
+ function isGitConfig(config) {
291
+ return config !== undefined && "type" in config && config.type === "git";
292
+ }
286
293
 
287
294
  class ResourceXError extends Error {
288
295
  constructor(message, options) {
@@ -1752,23 +1759,63 @@ class LocalRegistry {
1752
1759
  supportType(type) {
1753
1760
  this.typeHandler.register(type);
1754
1761
  }
1755
- buildPath(locator) {
1762
+ buildPath(locator, area) {
1756
1763
  const rxl = typeof locator === "string" ? parseRXL(locator) : locator;
1757
- const domain = rxl.domain ?? "localhost";
1764
+ const resourceName = rxl.type ? `${rxl.name}.${rxl.type}` : rxl.name;
1758
1765
  const version = rxl.version ?? "latest";
1759
- let path = join2(this.basePath, domain);
1760
- if (rxl.path) {
1761
- path = join2(path, rxl.path);
1766
+ if (area === "local") {
1767
+ return join2(this.basePath, "local", resourceName, version);
1768
+ } else {
1769
+ const domain = rxl.domain ?? "localhost";
1770
+ let path = join2(this.basePath, "cache", domain);
1771
+ if (rxl.path) {
1772
+ path = join2(path, rxl.path);
1773
+ }
1774
+ return join2(path, resourceName, version);
1762
1775
  }
1763
- const resourceName = rxl.type ? `${rxl.name}.${rxl.type}` : rxl.name;
1764
- return join2(path, resourceName, version);
1765
1776
  }
1766
- async publish(_resource) {
1767
- throw new RegistryError("Remote publish not implemented yet");
1777
+ isLocalOnlyLocator(locator) {
1778
+ const rxl = typeof locator === "string" ? parseRXL(locator) : locator;
1779
+ return !rxl.domain || rxl.domain === "localhost";
1780
+ }
1781
+ async existsAt(resourcePath) {
1782
+ const manifestPath = join2(resourcePath, "manifest.json");
1783
+ try {
1784
+ await stat2(manifestPath);
1785
+ return true;
1786
+ } catch {
1787
+ return false;
1788
+ }
1789
+ }
1790
+ async findArea(locator) {
1791
+ const localPath = this.buildPath(locator, "local");
1792
+ if (await this.existsAt(localPath)) {
1793
+ return "local";
1794
+ }
1795
+ const cachePath = this.buildPath(locator, "cache");
1796
+ if (await this.existsAt(cachePath)) {
1797
+ return "cache";
1798
+ }
1799
+ return null;
1800
+ }
1801
+ async loadFrom(resourcePath) {
1802
+ const manifestPath = join2(resourcePath, "manifest.json");
1803
+ const manifestContent = await readFile2(manifestPath, "utf-8");
1804
+ const manifestData = JSON.parse(manifestContent);
1805
+ const manifest = createRXM(manifestData);
1806
+ const contentPath = join2(resourcePath, "content.tar.gz");
1807
+ const data = await readFile2(contentPath);
1808
+ return this.typeHandler.deserialize(data, manifest);
1809
+ }
1810
+ async pull(_locator, _options) {
1811
+ throw new RegistryError("Pull not implemented yet - see issue #018");
1812
+ }
1813
+ async publish(_resource, _options) {
1814
+ throw new RegistryError("Publish not implemented yet - see issue #018");
1768
1815
  }
1769
1816
  async link(resource) {
1770
1817
  const locator = resource.manifest.toLocator();
1771
- const resourcePath = this.buildPath(locator);
1818
+ const resourcePath = this.buildPath(locator, "local");
1772
1819
  await mkdir2(resourcePath, { recursive: true });
1773
1820
  const manifestPath = join2(resourcePath, "manifest.json");
1774
1821
  await writeFile2(manifestPath, JSON.stringify(resource.manifest.toJSON(), null, 2), "utf-8");
@@ -1777,58 +1824,62 @@ class LocalRegistry {
1777
1824
  await writeFile2(contentPath, serialized);
1778
1825
  }
1779
1826
  async get(locator) {
1780
- if (!await this.exists(locator)) {
1827
+ const area = await this.findArea(locator);
1828
+ if (!area) {
1781
1829
  throw new RegistryError(`Resource not found: ${locator}`);
1782
1830
  }
1783
- const resourcePath = this.buildPath(locator);
1784
- const manifestPath = join2(resourcePath, "manifest.json");
1785
- const manifestContent = await readFile2(manifestPath, "utf-8");
1786
- const manifestData = JSON.parse(manifestContent);
1787
- const manifest = createRXM(manifestData);
1788
- const contentPath = join2(resourcePath, "content.tar.gz");
1789
- const data = await readFile2(contentPath);
1790
- return this.typeHandler.deserialize(data, manifest);
1831
+ const resourcePath = this.buildPath(locator, area);
1832
+ return this.loadFrom(resourcePath);
1791
1833
  }
1792
1834
  async resolve(locator) {
1793
1835
  const rxr = await this.get(locator);
1794
1836
  return this.typeHandler.resolve(rxr);
1795
1837
  }
1796
1838
  async exists(locator) {
1797
- const resourcePath = this.buildPath(locator);
1798
- const manifestPath = join2(resourcePath, "manifest.json");
1799
- try {
1800
- await stat2(manifestPath);
1801
- return true;
1802
- } catch {
1803
- return false;
1804
- }
1839
+ const area = await this.findArea(locator);
1840
+ return area !== null;
1805
1841
  }
1806
1842
  async delete(locator) {
1807
- if (!await this.exists(locator)) {
1808
- return;
1843
+ const isLocal = this.isLocalOnlyLocator(locator);
1844
+ if (isLocal) {
1845
+ const localPath = this.buildPath(locator, "local");
1846
+ if (await this.existsAt(localPath)) {
1847
+ await rm2(localPath, { recursive: true, force: true });
1848
+ }
1849
+ } else {
1850
+ const cachePath = this.buildPath(locator, "cache");
1851
+ if (await this.existsAt(cachePath)) {
1852
+ await rm2(cachePath, { recursive: true, force: true });
1853
+ }
1809
1854
  }
1810
- const resourcePath = this.buildPath(locator);
1811
- await rm2(resourcePath, { recursive: true, force: true });
1812
1855
  }
1813
1856
  async search(options) {
1814
1857
  const { query, limit, offset = 0 } = options ?? {};
1815
- let entries;
1816
- try {
1817
- entries = await this.listRecursive(this.basePath);
1818
- } catch {
1819
- return [];
1820
- }
1821
1858
  const locators = [];
1822
- for (const entry of entries) {
1823
- if (!entry.endsWith("manifest.json")) {
1824
- continue;
1859
+ const localDir = join2(this.basePath, "local");
1860
+ try {
1861
+ const localEntries = await this.listRecursive(localDir);
1862
+ for (const entry of localEntries) {
1863
+ if (!entry.endsWith("manifest.json"))
1864
+ continue;
1865
+ const relativePath = entry.slice(localDir.length + 1);
1866
+ const rxl = this.parseLocalEntry(relativePath);
1867
+ if (rxl)
1868
+ locators.push(rxl);
1825
1869
  }
1826
- const relativePath = entry.slice(this.basePath.length + 1);
1827
- const rxl = this.parseEntryToRXL(relativePath);
1828
- if (rxl) {
1829
- locators.push(rxl);
1870
+ } catch {}
1871
+ const cacheDir = join2(this.basePath, "cache");
1872
+ try {
1873
+ const cacheEntries = await this.listRecursive(cacheDir);
1874
+ for (const entry of cacheEntries) {
1875
+ if (!entry.endsWith("manifest.json"))
1876
+ continue;
1877
+ const relativePath = entry.slice(cacheDir.length + 1);
1878
+ const rxl = this.parseCacheEntry(relativePath);
1879
+ if (rxl)
1880
+ locators.push(rxl);
1830
1881
  }
1831
- }
1882
+ } catch {}
1832
1883
  let filtered = locators;
1833
1884
  if (query) {
1834
1885
  const lowerQuery = query.toLowerCase();
@@ -1859,7 +1910,27 @@ class LocalRegistry {
1859
1910
  } catch {}
1860
1911
  return results;
1861
1912
  }
1862
- parseEntryToRXL(entry) {
1913
+ parseLocalEntry(entry) {
1914
+ const dirPath = entry.replace(/[/\\]manifest\.json$/, "");
1915
+ const parts = dirPath.split(/[/\\]/);
1916
+ if (parts.length < 2) {
1917
+ return null;
1918
+ }
1919
+ const version = parts.pop();
1920
+ const nameTypePart = parts.shift();
1921
+ const { name, type } = this.parseNameType(nameTypePart);
1922
+ let locatorStr = name;
1923
+ if (type) {
1924
+ locatorStr += `.${type}`;
1925
+ }
1926
+ locatorStr += `@${version}`;
1927
+ try {
1928
+ return parseRXL(locatorStr);
1929
+ } catch {
1930
+ return null;
1931
+ }
1932
+ }
1933
+ parseCacheEntry(entry) {
1863
1934
  const dirPath = entry.replace(/[/\\]manifest\.json$/, "");
1864
1935
  const parts = dirPath.split(/[/\\]/);
1865
1936
  if (parts.length < 3) {
@@ -1869,16 +1940,7 @@ class LocalRegistry {
1869
1940
  const nameTypePart = parts.pop();
1870
1941
  const domain = parts.shift();
1871
1942
  const path = parts.length > 0 ? parts.join("/") : undefined;
1872
- const dotIndex = nameTypePart.lastIndexOf(".");
1873
- let name;
1874
- let type;
1875
- if (dotIndex !== -1) {
1876
- name = nameTypePart.substring(0, dotIndex);
1877
- type = nameTypePart.substring(dotIndex + 1);
1878
- } else {
1879
- name = nameTypePart;
1880
- type = undefined;
1881
- }
1943
+ const { name, type } = this.parseNameType(nameTypePart);
1882
1944
  let locatorStr = domain;
1883
1945
  if (path) {
1884
1946
  locatorStr += `/${path}`;
@@ -1894,6 +1956,17 @@ class LocalRegistry {
1894
1956
  return null;
1895
1957
  }
1896
1958
  }
1959
+ parseNameType(nameTypePart) {
1960
+ const dotIndex = nameTypePart.lastIndexOf(".");
1961
+ if (dotIndex !== -1) {
1962
+ return {
1963
+ name: nameTypePart.substring(0, dotIndex),
1964
+ type: nameTypePart.substring(dotIndex + 1)
1965
+ };
1966
+ } else {
1967
+ return { name: nameTypePart, type: undefined };
1968
+ }
1969
+ }
1897
1970
  }
1898
1971
 
1899
1972
  class RemoteRegistry {
@@ -1906,7 +1979,10 @@ class RemoteRegistry {
1906
1979
  supportType(type) {
1907
1980
  this.typeHandler.register(type);
1908
1981
  }
1909
- async publish(_resource) {
1982
+ async pull(_locator, _options) {
1983
+ throw new RegistryError("Cannot pull to remote registry - use local registry for pulling");
1984
+ }
1985
+ async publish(_resource, _options) {
1910
1986
  throw new RegistryError("Remote registry publish not implemented yet");
1911
1987
  }
1912
1988
  async link(_resource) {
@@ -1972,7 +2048,13 @@ async function discoverRegistry(domain) {
1972
2048
  throw new RegistryError(`Well-known discovery failed for ${domain}: ${response.statusText}`);
1973
2049
  }
1974
2050
  const data = await response.json();
1975
- return data.registry;
2051
+ if (!data.registries || !Array.isArray(data.registries) || data.registries.length === 0) {
2052
+ throw new RegistryError(`Invalid well-known response for ${domain}: missing or empty registries`);
2053
+ }
2054
+ return {
2055
+ domain,
2056
+ registries: data.registries
2057
+ };
1976
2058
  } catch (error) {
1977
2059
  if (error instanceof RegistryError) {
1978
2060
  throw error;
@@ -1980,10 +2062,210 @@ async function discoverRegistry(domain) {
1980
2062
  throw new RegistryError(`Failed to discover registry for ${domain}: ${error.message}`);
1981
2063
  }
1982
2064
  }
2065
+ var DEFAULT_GIT_CACHE = `${homedir2()}/.resourcex/.git-cache`;
2066
+
2067
+ class GitRegistry {
2068
+ url;
2069
+ ref;
2070
+ basePath;
2071
+ cacheDir;
2072
+ typeHandler;
2073
+ trustedDomain;
2074
+ constructor(config) {
2075
+ this.url = config.url;
2076
+ this.ref = config.ref ?? "main";
2077
+ this.basePath = config.basePath ?? ".resourcex";
2078
+ this.typeHandler = TypeHandlerChain.create();
2079
+ this.trustedDomain = config.domain;
2080
+ if (this.isRemoteUrl(config.url) && !config.domain) {
2081
+ throw new RegistryError(`Remote git registry requires a trusted domain.
2082
+
2083
+ ` + `Either:
2084
+ ` + `1. Use discoverRegistry("your-domain.com") to auto-bind domain
2085
+ ` + `2. Explicitly set domain: createRegistry({ type: "git", url: "...", domain: "your-domain.com" })
2086
+
2087
+ ` + `This ensures resources from untrusted sources cannot impersonate your domain.`);
2088
+ }
2089
+ this.cacheDir = this.buildCacheDir(config.url);
2090
+ }
2091
+ isRemoteUrl(url) {
2092
+ return url.startsWith("git@") || url.startsWith("https://") || url.startsWith("http://");
2093
+ }
2094
+ buildCacheDir(url) {
2095
+ let normalized = url;
2096
+ if (url.startsWith("git@")) {
2097
+ normalized = url.slice(4).replace(":", "/");
2098
+ }
2099
+ if (normalized.endsWith(".git")) {
2100
+ normalized = normalized.slice(0, -4);
2101
+ }
2102
+ const dirName = normalized.replace(/\//g, "-");
2103
+ return join22(DEFAULT_GIT_CACHE, dirName);
2104
+ }
2105
+ supportType(type) {
2106
+ this.typeHandler.register(type);
2107
+ }
2108
+ async ensureCloned() {
2109
+ const gitDir = join22(this.cacheDir, ".git");
2110
+ try {
2111
+ await stat22(gitDir);
2112
+ this.gitExec(`fetch origin ${this.ref}`);
2113
+ this.gitExec(`checkout FETCH_HEAD`);
2114
+ } catch {
2115
+ await mkdir22(DEFAULT_GIT_CACHE, { recursive: true });
2116
+ execSync(`git clone --depth 1 --branch ${this.ref} ${this.url} ${this.cacheDir}`, {
2117
+ stdio: "pipe"
2118
+ });
2119
+ }
2120
+ }
2121
+ gitExec(command) {
2122
+ execSync(`git -C ${this.cacheDir} ${command}`, { stdio: "pipe" });
2123
+ }
2124
+ buildResourcePath(locator) {
2125
+ const rxl = parseRXL(locator);
2126
+ const domain = rxl.domain ?? "localhost";
2127
+ const version = rxl.version ?? "latest";
2128
+ let path = join22(this.cacheDir, this.basePath, domain);
2129
+ if (rxl.path) {
2130
+ path = join22(path, rxl.path);
2131
+ }
2132
+ const resourceName = rxl.type ? `${rxl.name}.${rxl.type}` : rxl.name;
2133
+ return join22(path, resourceName, version);
2134
+ }
2135
+ async get(locator) {
2136
+ await this.ensureCloned();
2137
+ const resourcePath = this.buildResourcePath(locator);
2138
+ const manifestPath = join22(resourcePath, "manifest.json");
2139
+ try {
2140
+ await stat22(manifestPath);
2141
+ } catch {
2142
+ throw new RegistryError(`Resource not found: ${locator}`);
2143
+ }
2144
+ const manifestContent = await readFile22(manifestPath, "utf-8");
2145
+ const manifestData = JSON.parse(manifestContent);
2146
+ const manifest = createRXM(manifestData);
2147
+ if (this.trustedDomain && manifest.domain !== this.trustedDomain) {
2148
+ throw new RegistryError(`Untrusted domain: resource claims "${manifest.domain}" but registry only trusts "${this.trustedDomain}"`);
2149
+ }
2150
+ const contentPath = join22(resourcePath, "content.tar.gz");
2151
+ const data = await readFile22(contentPath);
2152
+ return this.typeHandler.deserialize(data, manifest);
2153
+ }
2154
+ async resolve(locator) {
2155
+ const rxr = await this.get(locator);
2156
+ return this.typeHandler.resolve(rxr);
2157
+ }
2158
+ async exists(locator) {
2159
+ try {
2160
+ await this.ensureCloned();
2161
+ const resourcePath = this.buildResourcePath(locator);
2162
+ const manifestPath = join22(resourcePath, "manifest.json");
2163
+ await stat22(manifestPath);
2164
+ return true;
2165
+ } catch {
2166
+ return false;
2167
+ }
2168
+ }
2169
+ async search(options) {
2170
+ await this.ensureCloned();
2171
+ const { query, limit, offset = 0 } = options ?? {};
2172
+ const locators = [];
2173
+ const baseDir = join22(this.cacheDir, this.basePath);
2174
+ try {
2175
+ const entries = await this.listRecursive(baseDir);
2176
+ for (const entry of entries) {
2177
+ if (!entry.endsWith("manifest.json"))
2178
+ continue;
2179
+ const relativePath = entry.slice(baseDir.length + 1);
2180
+ const rxl = this.parseEntryToRXL(relativePath);
2181
+ if (rxl)
2182
+ locators.push(rxl);
2183
+ }
2184
+ } catch {
2185
+ return [];
2186
+ }
2187
+ let filtered = locators;
2188
+ if (query) {
2189
+ const lowerQuery = query.toLowerCase();
2190
+ filtered = locators.filter((rxl) => {
2191
+ const searchText = `${rxl.domain ?? ""} ${rxl.path ?? ""} ${rxl.name} ${rxl.type ?? ""}`.toLowerCase();
2192
+ return searchText.includes(lowerQuery);
2193
+ });
2194
+ }
2195
+ let result = filtered.slice(offset);
2196
+ if (limit !== undefined) {
2197
+ result = result.slice(0, limit);
2198
+ }
2199
+ return result;
2200
+ }
2201
+ async listRecursive(dir) {
2202
+ const results = [];
2203
+ try {
2204
+ const entries = await readdir22(dir, { withFileTypes: true });
2205
+ for (const entry of entries) {
2206
+ const fullPath = join22(dir, entry.name);
2207
+ if (entry.isDirectory()) {
2208
+ const subEntries = await this.listRecursive(fullPath);
2209
+ results.push(...subEntries);
2210
+ } else {
2211
+ results.push(fullPath);
2212
+ }
2213
+ }
2214
+ } catch {}
2215
+ return results;
2216
+ }
2217
+ parseEntryToRXL(entry) {
2218
+ const dirPath = entry.replace(/[/\\]manifest\.json$/, "");
2219
+ const parts = dirPath.split(/[/\\]/);
2220
+ if (parts.length < 3)
2221
+ return null;
2222
+ const version = parts.pop();
2223
+ const nameTypePart = parts.pop();
2224
+ const domain = parts.shift();
2225
+ const path = parts.length > 0 ? parts.join("/") : undefined;
2226
+ const dotIndex = nameTypePart.lastIndexOf(".");
2227
+ let name;
2228
+ let type;
2229
+ if (dotIndex !== -1) {
2230
+ name = nameTypePart.substring(0, dotIndex);
2231
+ type = nameTypePart.substring(dotIndex + 1);
2232
+ } else {
2233
+ name = nameTypePart;
2234
+ type = undefined;
2235
+ }
2236
+ let locatorStr = domain;
2237
+ if (path)
2238
+ locatorStr += `/${path}`;
2239
+ locatorStr += `/${name}`;
2240
+ if (type)
2241
+ locatorStr += `.${type}`;
2242
+ locatorStr += `@${version}`;
2243
+ try {
2244
+ return parseRXL(locatorStr);
2245
+ } catch {
2246
+ return null;
2247
+ }
2248
+ }
2249
+ async pull(_locator, _options) {
2250
+ throw new RegistryError("GitRegistry is read-only - use LocalRegistry.pull()");
2251
+ }
2252
+ async publish(_resource, _options) {
2253
+ throw new RegistryError("GitRegistry is read-only - use LocalRegistry.publish()");
2254
+ }
2255
+ async link(_resource) {
2256
+ throw new RegistryError("GitRegistry is read-only - use LocalRegistry.link()");
2257
+ }
2258
+ async delete(_locator) {
2259
+ throw new RegistryError("GitRegistry is read-only - use LocalRegistry.delete()");
2260
+ }
2261
+ }
1983
2262
  function createRegistry(config) {
1984
2263
  if (isRemoteConfig(config)) {
1985
2264
  return new RemoteRegistry(config);
1986
2265
  }
2266
+ if (isGitConfig(config)) {
2267
+ return new GitRegistry(config);
2268
+ }
1987
2269
  return new LocalRegistry(config);
1988
2270
  }
1989
2271
  var registryCache = new Map;
@@ -2037,8 +2319,17 @@ class RxrTransport {
2037
2319
  registry = createRegistry();
2038
2320
  } else {
2039
2321
  try {
2040
- const endpoint = await discoverRegistry(domain);
2041
- registry = createRegistry({ endpoint });
2322
+ const discovery = await discoverRegistry(domain);
2323
+ const registryUrl = discovery.registries[0];
2324
+ if (this.isGitUrl(registryUrl)) {
2325
+ registry = createRegistry({
2326
+ type: "git",
2327
+ url: registryUrl,
2328
+ domain: discovery.domain
2329
+ });
2330
+ } else {
2331
+ registry = createRegistry({ endpoint: registryUrl });
2332
+ }
2042
2333
  } catch (error) {
2043
2334
  throw new TransportError(`Failed to discover registry for domain ${domain}: ${error.message}`, this.name);
2044
2335
  }
@@ -2046,6 +2337,9 @@ class RxrTransport {
2046
2337
  registryCache.set(domain, registry);
2047
2338
  return registry;
2048
2339
  }
2340
+ isGitUrl(url) {
2341
+ return url.startsWith("git@") || url.endsWith(".git");
2342
+ }
2049
2343
  parseLocation(location) {
2050
2344
  const atIndex = location.indexOf("@");
2051
2345
  if (atIndex === -1) {
@@ -2263,7 +2557,7 @@ class ARP {
2263
2557
  function createARP(config) {
2264
2558
  return new ARP(config);
2265
2559
  }
2266
- var VERSION = "1.7.0";
2560
+ var VERSION = "2.0.0";
2267
2561
  export {
2268
2562
  textSemantic,
2269
2563
  httpsTransport,
@@ -2285,4 +2579,4 @@ export {
2285
2579
  ARP
2286
2580
  };
2287
2581
 
2288
- //# debugId=FC92A0992F1DA3E764756E2164756E21
2582
+ //# debugId=CFFA41D50CE0FA3A64756E2164756E21