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/index.js CHANGED
@@ -2502,9 +2502,16 @@ import { join } from "node:path";
2502
2502
  import { readFile, writeFile, mkdir, rm, stat, readdir } from "node:fs/promises";
2503
2503
  import { gzip as gzip22, gunzip as gunzip22 } from "node:zlib";
2504
2504
  import { promisify as promisify22 } from "node:util";
2505
+ import { homedir as homedir2 } from "node:os";
2506
+ import { join as join2 } from "node:path";
2507
+ import { readFile as readFile2, stat as stat2, readdir as readdir2, mkdir as mkdir2 } from "node:fs/promises";
2508
+ import { execSync } from "node:child_process";
2505
2509
  function isRemoteConfig(config) {
2506
2510
  return config !== undefined && "endpoint" in config;
2507
2511
  }
2512
+ function isGitConfig(config) {
2513
+ return config !== undefined && "type" in config && config.type === "git";
2514
+ }
2508
2515
 
2509
2516
  class ResourceXError3 extends Error {
2510
2517
  constructor(message, options) {
@@ -3974,23 +3981,63 @@ class LocalRegistry {
3974
3981
  supportType(type) {
3975
3982
  this.typeHandler.register(type);
3976
3983
  }
3977
- buildPath(locator) {
3984
+ buildPath(locator, area) {
3978
3985
  const rxl = typeof locator === "string" ? parseRXL3(locator) : locator;
3979
- const domain = rxl.domain ?? "localhost";
3986
+ const resourceName = rxl.type ? `${rxl.name}.${rxl.type}` : rxl.name;
3980
3987
  const version = rxl.version ?? "latest";
3981
- let path = join(this.basePath, domain);
3982
- if (rxl.path) {
3983
- path = join(path, rxl.path);
3988
+ if (area === "local") {
3989
+ return join(this.basePath, "local", resourceName, version);
3990
+ } else {
3991
+ const domain = rxl.domain ?? "localhost";
3992
+ let path = join(this.basePath, "cache", domain);
3993
+ if (rxl.path) {
3994
+ path = join(path, rxl.path);
3995
+ }
3996
+ return join(path, resourceName, version);
3984
3997
  }
3985
- const resourceName = rxl.type ? `${rxl.name}.${rxl.type}` : rxl.name;
3986
- return join(path, resourceName, version);
3987
3998
  }
3988
- async publish(_resource) {
3989
- throw new RegistryError("Remote publish not implemented yet");
3999
+ isLocalOnlyLocator(locator) {
4000
+ const rxl = typeof locator === "string" ? parseRXL3(locator) : locator;
4001
+ return !rxl.domain || rxl.domain === "localhost";
4002
+ }
4003
+ async existsAt(resourcePath) {
4004
+ const manifestPath = join(resourcePath, "manifest.json");
4005
+ try {
4006
+ await stat(manifestPath);
4007
+ return true;
4008
+ } catch {
4009
+ return false;
4010
+ }
4011
+ }
4012
+ async findArea(locator) {
4013
+ const localPath = this.buildPath(locator, "local");
4014
+ if (await this.existsAt(localPath)) {
4015
+ return "local";
4016
+ }
4017
+ const cachePath = this.buildPath(locator, "cache");
4018
+ if (await this.existsAt(cachePath)) {
4019
+ return "cache";
4020
+ }
4021
+ return null;
4022
+ }
4023
+ async loadFrom(resourcePath) {
4024
+ const manifestPath = join(resourcePath, "manifest.json");
4025
+ const manifestContent = await readFile(manifestPath, "utf-8");
4026
+ const manifestData = JSON.parse(manifestContent);
4027
+ const manifest = createRXM2(manifestData);
4028
+ const contentPath = join(resourcePath, "content.tar.gz");
4029
+ const data = await readFile(contentPath);
4030
+ return this.typeHandler.deserialize(data, manifest);
4031
+ }
4032
+ async pull(_locator, _options) {
4033
+ throw new RegistryError("Pull not implemented yet - see issue #018");
4034
+ }
4035
+ async publish(_resource, _options) {
4036
+ throw new RegistryError("Publish not implemented yet - see issue #018");
3990
4037
  }
3991
4038
  async link(resource) {
3992
4039
  const locator = resource.manifest.toLocator();
3993
- const resourcePath = this.buildPath(locator);
4040
+ const resourcePath = this.buildPath(locator, "local");
3994
4041
  await mkdir(resourcePath, { recursive: true });
3995
4042
  const manifestPath = join(resourcePath, "manifest.json");
3996
4043
  await writeFile(manifestPath, JSON.stringify(resource.manifest.toJSON(), null, 2), "utf-8");
@@ -3999,58 +4046,62 @@ class LocalRegistry {
3999
4046
  await writeFile(contentPath, serialized);
4000
4047
  }
4001
4048
  async get(locator) {
4002
- if (!await this.exists(locator)) {
4049
+ const area = await this.findArea(locator);
4050
+ if (!area) {
4003
4051
  throw new RegistryError(`Resource not found: ${locator}`);
4004
4052
  }
4005
- const resourcePath = this.buildPath(locator);
4006
- const manifestPath = join(resourcePath, "manifest.json");
4007
- const manifestContent = await readFile(manifestPath, "utf-8");
4008
- const manifestData = JSON.parse(manifestContent);
4009
- const manifest = createRXM2(manifestData);
4010
- const contentPath = join(resourcePath, "content.tar.gz");
4011
- const data = await readFile(contentPath);
4012
- return this.typeHandler.deserialize(data, manifest);
4053
+ const resourcePath = this.buildPath(locator, area);
4054
+ return this.loadFrom(resourcePath);
4013
4055
  }
4014
4056
  async resolve(locator) {
4015
4057
  const rxr = await this.get(locator);
4016
4058
  return this.typeHandler.resolve(rxr);
4017
4059
  }
4018
4060
  async exists(locator) {
4019
- const resourcePath = this.buildPath(locator);
4020
- const manifestPath = join(resourcePath, "manifest.json");
4021
- try {
4022
- await stat(manifestPath);
4023
- return true;
4024
- } catch {
4025
- return false;
4026
- }
4061
+ const area = await this.findArea(locator);
4062
+ return area !== null;
4027
4063
  }
4028
4064
  async delete(locator) {
4029
- if (!await this.exists(locator)) {
4030
- return;
4065
+ const isLocal = this.isLocalOnlyLocator(locator);
4066
+ if (isLocal) {
4067
+ const localPath = this.buildPath(locator, "local");
4068
+ if (await this.existsAt(localPath)) {
4069
+ await rm(localPath, { recursive: true, force: true });
4070
+ }
4071
+ } else {
4072
+ const cachePath = this.buildPath(locator, "cache");
4073
+ if (await this.existsAt(cachePath)) {
4074
+ await rm(cachePath, { recursive: true, force: true });
4075
+ }
4031
4076
  }
4032
- const resourcePath = this.buildPath(locator);
4033
- await rm(resourcePath, { recursive: true, force: true });
4034
4077
  }
4035
4078
  async search(options) {
4036
4079
  const { query, limit, offset = 0 } = options ?? {};
4037
- let entries;
4038
- try {
4039
- entries = await this.listRecursive(this.basePath);
4040
- } catch {
4041
- return [];
4042
- }
4043
4080
  const locators = [];
4044
- for (const entry of entries) {
4045
- if (!entry.endsWith("manifest.json")) {
4046
- continue;
4081
+ const localDir = join(this.basePath, "local");
4082
+ try {
4083
+ const localEntries = await this.listRecursive(localDir);
4084
+ for (const entry of localEntries) {
4085
+ if (!entry.endsWith("manifest.json"))
4086
+ continue;
4087
+ const relativePath = entry.slice(localDir.length + 1);
4088
+ const rxl = this.parseLocalEntry(relativePath);
4089
+ if (rxl)
4090
+ locators.push(rxl);
4047
4091
  }
4048
- const relativePath = entry.slice(this.basePath.length + 1);
4049
- const rxl = this.parseEntryToRXL(relativePath);
4050
- if (rxl) {
4051
- locators.push(rxl);
4092
+ } catch {}
4093
+ const cacheDir = join(this.basePath, "cache");
4094
+ try {
4095
+ const cacheEntries = await this.listRecursive(cacheDir);
4096
+ for (const entry of cacheEntries) {
4097
+ if (!entry.endsWith("manifest.json"))
4098
+ continue;
4099
+ const relativePath = entry.slice(cacheDir.length + 1);
4100
+ const rxl = this.parseCacheEntry(relativePath);
4101
+ if (rxl)
4102
+ locators.push(rxl);
4052
4103
  }
4053
- }
4104
+ } catch {}
4054
4105
  let filtered = locators;
4055
4106
  if (query) {
4056
4107
  const lowerQuery = query.toLowerCase();
@@ -4081,7 +4132,27 @@ class LocalRegistry {
4081
4132
  } catch {}
4082
4133
  return results;
4083
4134
  }
4084
- parseEntryToRXL(entry) {
4135
+ parseLocalEntry(entry) {
4136
+ const dirPath = entry.replace(/[/\\]manifest\.json$/, "");
4137
+ const parts = dirPath.split(/[/\\]/);
4138
+ if (parts.length < 2) {
4139
+ return null;
4140
+ }
4141
+ const version = parts.pop();
4142
+ const nameTypePart = parts.shift();
4143
+ const { name, type } = this.parseNameType(nameTypePart);
4144
+ let locatorStr = name;
4145
+ if (type) {
4146
+ locatorStr += `.${type}`;
4147
+ }
4148
+ locatorStr += `@${version}`;
4149
+ try {
4150
+ return parseRXL3(locatorStr);
4151
+ } catch {
4152
+ return null;
4153
+ }
4154
+ }
4155
+ parseCacheEntry(entry) {
4085
4156
  const dirPath = entry.replace(/[/\\]manifest\.json$/, "");
4086
4157
  const parts = dirPath.split(/[/\\]/);
4087
4158
  if (parts.length < 3) {
@@ -4091,16 +4162,7 @@ class LocalRegistry {
4091
4162
  const nameTypePart = parts.pop();
4092
4163
  const domain = parts.shift();
4093
4164
  const path = parts.length > 0 ? parts.join("/") : undefined;
4094
- const dotIndex = nameTypePart.lastIndexOf(".");
4095
- let name;
4096
- let type;
4097
- if (dotIndex !== -1) {
4098
- name = nameTypePart.substring(0, dotIndex);
4099
- type = nameTypePart.substring(dotIndex + 1);
4100
- } else {
4101
- name = nameTypePart;
4102
- type = undefined;
4103
- }
4165
+ const { name, type } = this.parseNameType(nameTypePart);
4104
4166
  let locatorStr = domain;
4105
4167
  if (path) {
4106
4168
  locatorStr += `/${path}`;
@@ -4116,6 +4178,17 @@ class LocalRegistry {
4116
4178
  return null;
4117
4179
  }
4118
4180
  }
4181
+ parseNameType(nameTypePart) {
4182
+ const dotIndex = nameTypePart.lastIndexOf(".");
4183
+ if (dotIndex !== -1) {
4184
+ return {
4185
+ name: nameTypePart.substring(0, dotIndex),
4186
+ type: nameTypePart.substring(dotIndex + 1)
4187
+ };
4188
+ } else {
4189
+ return { name: nameTypePart, type: undefined };
4190
+ }
4191
+ }
4119
4192
  }
4120
4193
 
4121
4194
  class RemoteRegistry {
@@ -4128,7 +4201,10 @@ class RemoteRegistry {
4128
4201
  supportType(type) {
4129
4202
  this.typeHandler.register(type);
4130
4203
  }
4131
- async publish(_resource) {
4204
+ async pull(_locator, _options) {
4205
+ throw new RegistryError("Cannot pull to remote registry - use local registry for pulling");
4206
+ }
4207
+ async publish(_resource, _options) {
4132
4208
  throw new RegistryError("Remote registry publish not implemented yet");
4133
4209
  }
4134
4210
  async link(_resource) {
@@ -4186,15 +4262,215 @@ class RemoteRegistry {
4186
4262
  return (data.results || []).map((locator) => parseRXL3(locator));
4187
4263
  }
4188
4264
  }
4265
+ var DEFAULT_GIT_CACHE = `${homedir2()}/.resourcex/.git-cache`;
4266
+
4267
+ class GitRegistry {
4268
+ url;
4269
+ ref;
4270
+ basePath;
4271
+ cacheDir;
4272
+ typeHandler;
4273
+ trustedDomain;
4274
+ constructor(config) {
4275
+ this.url = config.url;
4276
+ this.ref = config.ref ?? "main";
4277
+ this.basePath = config.basePath ?? ".resourcex";
4278
+ this.typeHandler = TypeHandlerChain2.create();
4279
+ this.trustedDomain = config.domain;
4280
+ if (this.isRemoteUrl(config.url) && !config.domain) {
4281
+ throw new RegistryError(`Remote git registry requires a trusted domain.
4282
+
4283
+ ` + `Either:
4284
+ ` + `1. Use discoverRegistry("your-domain.com") to auto-bind domain
4285
+ ` + `2. Explicitly set domain: createRegistry({ type: "git", url: "...", domain: "your-domain.com" })
4286
+
4287
+ ` + `This ensures resources from untrusted sources cannot impersonate your domain.`);
4288
+ }
4289
+ this.cacheDir = this.buildCacheDir(config.url);
4290
+ }
4291
+ isRemoteUrl(url) {
4292
+ return url.startsWith("git@") || url.startsWith("https://") || url.startsWith("http://");
4293
+ }
4294
+ buildCacheDir(url) {
4295
+ let normalized = url;
4296
+ if (url.startsWith("git@")) {
4297
+ normalized = url.slice(4).replace(":", "/");
4298
+ }
4299
+ if (normalized.endsWith(".git")) {
4300
+ normalized = normalized.slice(0, -4);
4301
+ }
4302
+ const dirName = normalized.replace(/\//g, "-");
4303
+ return join2(DEFAULT_GIT_CACHE, dirName);
4304
+ }
4305
+ supportType(type) {
4306
+ this.typeHandler.register(type);
4307
+ }
4308
+ async ensureCloned() {
4309
+ const gitDir = join2(this.cacheDir, ".git");
4310
+ try {
4311
+ await stat2(gitDir);
4312
+ this.gitExec(`fetch origin ${this.ref}`);
4313
+ this.gitExec(`checkout FETCH_HEAD`);
4314
+ } catch {
4315
+ await mkdir2(DEFAULT_GIT_CACHE, { recursive: true });
4316
+ execSync(`git clone --depth 1 --branch ${this.ref} ${this.url} ${this.cacheDir}`, {
4317
+ stdio: "pipe"
4318
+ });
4319
+ }
4320
+ }
4321
+ gitExec(command) {
4322
+ execSync(`git -C ${this.cacheDir} ${command}`, { stdio: "pipe" });
4323
+ }
4324
+ buildResourcePath(locator) {
4325
+ const rxl = parseRXL3(locator);
4326
+ const domain = rxl.domain ?? "localhost";
4327
+ const version = rxl.version ?? "latest";
4328
+ let path = join2(this.cacheDir, this.basePath, domain);
4329
+ if (rxl.path) {
4330
+ path = join2(path, rxl.path);
4331
+ }
4332
+ const resourceName = rxl.type ? `${rxl.name}.${rxl.type}` : rxl.name;
4333
+ return join2(path, resourceName, version);
4334
+ }
4335
+ async get(locator) {
4336
+ await this.ensureCloned();
4337
+ const resourcePath = this.buildResourcePath(locator);
4338
+ const manifestPath = join2(resourcePath, "manifest.json");
4339
+ try {
4340
+ await stat2(manifestPath);
4341
+ } catch {
4342
+ throw new RegistryError(`Resource not found: ${locator}`);
4343
+ }
4344
+ const manifestContent = await readFile2(manifestPath, "utf-8");
4345
+ const manifestData = JSON.parse(manifestContent);
4346
+ const manifest = createRXM2(manifestData);
4347
+ if (this.trustedDomain && manifest.domain !== this.trustedDomain) {
4348
+ throw new RegistryError(`Untrusted domain: resource claims "${manifest.domain}" but registry only trusts "${this.trustedDomain}"`);
4349
+ }
4350
+ const contentPath = join2(resourcePath, "content.tar.gz");
4351
+ const data = await readFile2(contentPath);
4352
+ return this.typeHandler.deserialize(data, manifest);
4353
+ }
4354
+ async resolve(locator) {
4355
+ const rxr = await this.get(locator);
4356
+ return this.typeHandler.resolve(rxr);
4357
+ }
4358
+ async exists(locator) {
4359
+ try {
4360
+ await this.ensureCloned();
4361
+ const resourcePath = this.buildResourcePath(locator);
4362
+ const manifestPath = join2(resourcePath, "manifest.json");
4363
+ await stat2(manifestPath);
4364
+ return true;
4365
+ } catch {
4366
+ return false;
4367
+ }
4368
+ }
4369
+ async search(options) {
4370
+ await this.ensureCloned();
4371
+ const { query, limit, offset = 0 } = options ?? {};
4372
+ const locators = [];
4373
+ const baseDir = join2(this.cacheDir, this.basePath);
4374
+ try {
4375
+ const entries = await this.listRecursive(baseDir);
4376
+ for (const entry of entries) {
4377
+ if (!entry.endsWith("manifest.json"))
4378
+ continue;
4379
+ const relativePath = entry.slice(baseDir.length + 1);
4380
+ const rxl = this.parseEntryToRXL(relativePath);
4381
+ if (rxl)
4382
+ locators.push(rxl);
4383
+ }
4384
+ } catch {
4385
+ return [];
4386
+ }
4387
+ let filtered = locators;
4388
+ if (query) {
4389
+ const lowerQuery = query.toLowerCase();
4390
+ filtered = locators.filter((rxl) => {
4391
+ const searchText = `${rxl.domain ?? ""} ${rxl.path ?? ""} ${rxl.name} ${rxl.type ?? ""}`.toLowerCase();
4392
+ return searchText.includes(lowerQuery);
4393
+ });
4394
+ }
4395
+ let result = filtered.slice(offset);
4396
+ if (limit !== undefined) {
4397
+ result = result.slice(0, limit);
4398
+ }
4399
+ return result;
4400
+ }
4401
+ async listRecursive(dir) {
4402
+ const results = [];
4403
+ try {
4404
+ const entries = await readdir2(dir, { withFileTypes: true });
4405
+ for (const entry of entries) {
4406
+ const fullPath = join2(dir, entry.name);
4407
+ if (entry.isDirectory()) {
4408
+ const subEntries = await this.listRecursive(fullPath);
4409
+ results.push(...subEntries);
4410
+ } else {
4411
+ results.push(fullPath);
4412
+ }
4413
+ }
4414
+ } catch {}
4415
+ return results;
4416
+ }
4417
+ parseEntryToRXL(entry) {
4418
+ const dirPath = entry.replace(/[/\\]manifest\.json$/, "");
4419
+ const parts = dirPath.split(/[/\\]/);
4420
+ if (parts.length < 3)
4421
+ return null;
4422
+ const version = parts.pop();
4423
+ const nameTypePart = parts.pop();
4424
+ const domain = parts.shift();
4425
+ const path = parts.length > 0 ? parts.join("/") : undefined;
4426
+ const dotIndex = nameTypePart.lastIndexOf(".");
4427
+ let name;
4428
+ let type;
4429
+ if (dotIndex !== -1) {
4430
+ name = nameTypePart.substring(0, dotIndex);
4431
+ type = nameTypePart.substring(dotIndex + 1);
4432
+ } else {
4433
+ name = nameTypePart;
4434
+ type = undefined;
4435
+ }
4436
+ let locatorStr = domain;
4437
+ if (path)
4438
+ locatorStr += `/${path}`;
4439
+ locatorStr += `/${name}`;
4440
+ if (type)
4441
+ locatorStr += `.${type}`;
4442
+ locatorStr += `@${version}`;
4443
+ try {
4444
+ return parseRXL3(locatorStr);
4445
+ } catch {
4446
+ return null;
4447
+ }
4448
+ }
4449
+ async pull(_locator, _options) {
4450
+ throw new RegistryError("GitRegistry is read-only - use LocalRegistry.pull()");
4451
+ }
4452
+ async publish(_resource, _options) {
4453
+ throw new RegistryError("GitRegistry is read-only - use LocalRegistry.publish()");
4454
+ }
4455
+ async link(_resource) {
4456
+ throw new RegistryError("GitRegistry is read-only - use LocalRegistry.link()");
4457
+ }
4458
+ async delete(_locator) {
4459
+ throw new RegistryError("GitRegistry is read-only - use LocalRegistry.delete()");
4460
+ }
4461
+ }
4189
4462
  function createRegistry(config) {
4190
4463
  if (isRemoteConfig(config)) {
4191
4464
  return new RemoteRegistry(config);
4192
4465
  }
4466
+ if (isGitConfig(config)) {
4467
+ return new GitRegistry(config);
4468
+ }
4193
4469
  return new LocalRegistry(config);
4194
4470
  }
4195
4471
  // ../loader/dist/index.js
4196
- import { join as join2, relative } from "node:path";
4197
- import { stat as stat2, readFile as readFile2, readdir as readdir2 } from "node:fs/promises";
4472
+ import { join as join3, relative } from "node:path";
4473
+ import { stat as stat3, readFile as readFile3, readdir as readdir3 } from "node:fs/promises";
4198
4474
  import { gzip as gzip4, gunzip as gunzip4 } from "node:zlib";
4199
4475
  import { promisify as promisify4 } from "node:util";
4200
4476
 
@@ -5386,22 +5662,22 @@ async function createRXC4(input) {
5386
5662
  class FolderLoader {
5387
5663
  async canLoad(source) {
5388
5664
  try {
5389
- const stats = await stat2(source);
5665
+ const stats = await stat3(source);
5390
5666
  if (!stats.isDirectory()) {
5391
5667
  return false;
5392
5668
  }
5393
- const manifestPath = join2(source, "resource.json");
5394
- const manifestStats = await stat2(manifestPath);
5669
+ const manifestPath = join3(source, "resource.json");
5670
+ const manifestStats = await stat3(manifestPath);
5395
5671
  return manifestStats.isFile();
5396
5672
  } catch {
5397
5673
  return false;
5398
5674
  }
5399
5675
  }
5400
5676
  async load(folderPath) {
5401
- const manifestPath = join2(folderPath, "resource.json");
5677
+ const manifestPath = join3(folderPath, "resource.json");
5402
5678
  let manifestJson;
5403
5679
  try {
5404
- manifestJson = await readFile2(manifestPath, "utf-8");
5680
+ manifestJson = await readFile3(manifestPath, "utf-8");
5405
5681
  } catch (error) {
5406
5682
  throw new ResourceXError4(`Failed to read resource.json: ${error instanceof Error ? error.message : String(error)}`);
5407
5683
  }
@@ -5441,15 +5717,15 @@ class FolderLoader {
5441
5717
  }
5442
5718
  async readFolderFiles(folderPath, basePath = folderPath) {
5443
5719
  const files = {};
5444
- const entries = await readdir2(folderPath, { withFileTypes: true });
5720
+ const entries = await readdir3(folderPath, { withFileTypes: true });
5445
5721
  for (const entry of entries) {
5446
- const fullPath = join2(folderPath, entry.name);
5722
+ const fullPath = join3(folderPath, entry.name);
5447
5723
  const relativePath = relative(basePath, fullPath);
5448
5724
  if (relativePath === "resource.json") {
5449
5725
  continue;
5450
5726
  }
5451
5727
  if (entry.isFile()) {
5452
- files[relativePath] = await readFile2(fullPath);
5728
+ files[relativePath] = await readFile3(fullPath);
5453
5729
  } else if (entry.isDirectory()) {
5454
5730
  const subFiles = await this.readFolderFiles(fullPath, basePath);
5455
5731
  Object.assign(files, subFiles);
@@ -5468,7 +5744,7 @@ async function loadResource(source, config) {
5468
5744
  }
5469
5745
 
5470
5746
  // src/index.ts
5471
- var VERSION = "1.7.0";
5747
+ var VERSION = "2.0.0";
5472
5748
  export {
5473
5749
  textType,
5474
5750
  parseRXL,
@@ -5491,4 +5767,4 @@ export {
5491
5767
  ContentError
5492
5768
  };
5493
5769
 
5494
- //# debugId=D41A861C6C8E364564756E2164756E21
5770
+ //# debugId=3C36FF6A5291529664756E2164756E21