nx-content-store-server 1.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/README.md +107 -0
- package/dist/adapters/BackendAdapterRegistry.d.ts +13 -0
- package/dist/adapters/BackendAdapterRegistry.d.ts.map +1 -0
- package/dist/adapters/BackendAdapterRegistry.js +19 -0
- package/dist/adapters/BackendAdapterRegistry.js.map +1 -0
- package/dist/adapters/nxContent/NxContentBackendAdapter.d.ts +21 -0
- package/dist/adapters/nxContent/NxContentBackendAdapter.d.ts.map +1 -0
- package/dist/adapters/nxContent/NxContentBackendAdapter.js +89 -0
- package/dist/adapters/nxContent/NxContentBackendAdapter.js.map +1 -0
- package/dist/adapters/nxContent/NxContentBindingFactory.d.ts +8 -0
- package/dist/adapters/nxContent/NxContentBindingFactory.d.ts.map +1 -0
- package/dist/adapters/nxContent/NxContentBindingFactory.js +29 -0
- package/dist/adapters/nxContent/NxContentBindingFactory.js.map +1 -0
- package/dist/adapters/nxContent/compare.d.ts +18 -0
- package/dist/adapters/nxContent/compare.d.ts.map +1 -0
- package/dist/adapters/nxContent/compare.js +71 -0
- package/dist/adapters/nxContent/compare.js.map +1 -0
- package/dist/adapters/nxContent/importExport.d.ts +36 -0
- package/dist/adapters/nxContent/importExport.d.ts.map +1 -0
- package/dist/adapters/nxContent/importExport.js +124 -0
- package/dist/adapters/nxContent/importExport.js.map +1 -0
- package/dist/adapters/nxContent/mapping.d.ts +36 -0
- package/dist/adapters/nxContent/mapping.d.ts.map +1 -0
- package/dist/adapters/nxContent/mapping.js +85 -0
- package/dist/adapters/nxContent/mapping.js.map +1 -0
- package/dist/adapters/nxContent/publish.d.ts +8 -0
- package/dist/adapters/nxContent/publish.d.ts.map +1 -0
- package/dist/adapters/nxContent/publish.js +17 -0
- package/dist/adapters/nxContent/publish.js.map +1 -0
- package/dist/adapters/types.d.ts +76 -0
- package/dist/adapters/types.d.ts.map +1 -0
- package/dist/adapters/types.js +2 -0
- package/dist/adapters/types.js.map +1 -0
- package/dist/app.d.ts +14 -0
- package/dist/app.d.ts.map +1 -0
- package/dist/app.js +85 -0
- package/dist/app.js.map +1 -0
- package/dist/config/types.d.ts +16 -0
- package/dist/config/types.d.ts.map +1 -0
- package/dist/config/types.js +2 -0
- package/dist/config/types.js.map +1 -0
- package/dist/handlers/publish.handlers.d.ts +6 -0
- package/dist/handlers/publish.handlers.d.ts.map +1 -0
- package/dist/handlers/publish.handlers.js +8 -0
- package/dist/handlers/publish.handlers.js.map +1 -0
- package/dist/handlers/sync.handlers.d.ts +31 -0
- package/dist/handlers/sync.handlers.d.ts.map +1 -0
- package/dist/handlers/sync.handlers.js +20 -0
- package/dist/handlers/sync.handlers.js.map +1 -0
- package/dist/index.d.ts +25 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +22 -0
- package/dist/index.js.map +1 -0
- package/dist/middleware/authorize.d.ts +23 -0
- package/dist/middleware/authorize.d.ts.map +1 -0
- package/dist/middleware/authorize.js +32 -0
- package/dist/middleware/authorize.js.map +1 -0
- package/dist/persistence/InMemorySyncRunPersistence.d.ts +14 -0
- package/dist/persistence/InMemorySyncRunPersistence.d.ts.map +1 -0
- package/dist/persistence/InMemorySyncRunPersistence.js +25 -0
- package/dist/persistence/InMemorySyncRunPersistence.js.map +1 -0
- package/dist/routes/backends.d.ts +6 -0
- package/dist/routes/backends.d.ts.map +1 -0
- package/dist/routes/backends.js +6 -0
- package/dist/routes/backends.js.map +1 -0
- package/dist/routes/sync.d.ts +17 -0
- package/dist/routes/sync.d.ts.map +1 -0
- package/dist/routes/sync.js +7 -0
- package/dist/routes/sync.js.map +1 -0
- package/dist/services/CompareApplicationService.d.ts +12 -0
- package/dist/services/CompareApplicationService.d.ts.map +1 -0
- package/dist/services/CompareApplicationService.js +33 -0
- package/dist/services/CompareApplicationService.js.map +1 -0
- package/dist/services/PublishApplicationService.d.ts +15 -0
- package/dist/services/PublishApplicationService.d.ts.map +1 -0
- package/dist/services/PublishApplicationService.js +29 -0
- package/dist/services/PublishApplicationService.js.map +1 -0
- package/dist/services/SyncApplicationService.d.ts +18 -0
- package/dist/services/SyncApplicationService.d.ts.map +1 -0
- package/dist/services/SyncApplicationService.js +103 -0
- package/dist/services/SyncApplicationService.js.map +1 -0
- package/dist/services/types.d.ts +19 -0
- package/dist/services/types.d.ts.map +1 -0
- package/dist/services/types.js +2 -0
- package/dist/services/types.js.map +1 -0
- package/dist/validation/backends.d.ts +8 -0
- package/dist/validation/backends.d.ts.map +1 -0
- package/dist/validation/backends.js +20 -0
- package/dist/validation/backends.js.map +1 -0
- package/dist/validation/sync.d.ts +27 -0
- package/dist/validation/sync.d.ts.map +1 -0
- package/dist/validation/sync.js +39 -0
- package/dist/validation/sync.js.map +1 -0
- package/package.json +32 -0
package/README.md
ADDED
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
# nx-content-store-server
|
|
2
|
+
|
|
3
|
+
HTTP server for **nx-content-store**: stores, keys, content, revisions, backends, sync, compare, and publish. Extends the managed content store with an **nx-content** backend adapter, sync/import/export, compare/drift, and publish (push to remote).
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install nx-content-store-server nx-content-store
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Usage
|
|
12
|
+
|
|
13
|
+
Wire the server to your Express app and a `ManagedContentStore` instance (from `nx-content-store` with your persistence adapter):
|
|
14
|
+
|
|
15
|
+
```ts
|
|
16
|
+
import express from 'express';
|
|
17
|
+
import { createManagedContentStore } from 'nx-content-store';
|
|
18
|
+
import { registerSyncAndPublishRoutes, InMemorySyncRunPersistence } from 'nx-content-store-server';
|
|
19
|
+
|
|
20
|
+
const app = express();
|
|
21
|
+
app.use(express.json());
|
|
22
|
+
|
|
23
|
+
const store = createManagedContentStore({ persistence: myPersistenceAdapter });
|
|
24
|
+
const syncRunPersistence = new InMemorySyncRunPersistence(); // or your SyncRunPersistence impl
|
|
25
|
+
|
|
26
|
+
registerSyncAndPublishRoutes(app, {
|
|
27
|
+
store,
|
|
28
|
+
syncRunPersistence,
|
|
29
|
+
audit: myAuditEmitter, // optional
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
app.listen(3000);
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## Routes (Step 5)
|
|
36
|
+
|
|
37
|
+
All of these require **secret or admin** auth (e.g. `x-api-key: secret-...` or `Authorization: Bearer <token>`).
|
|
38
|
+
|
|
39
|
+
| Method | Path | Description |
|
|
40
|
+
|--------|------|-------------|
|
|
41
|
+
| POST | `/stores/:storeId/sync` | Run sync between source and target backend |
|
|
42
|
+
| GET | `/stores/:storeId/sync-runs` | List sync runs |
|
|
43
|
+
| GET | `/stores/:storeId/sync-runs/:syncRunId` | Get a sync run |
|
|
44
|
+
| POST | `/stores/:storeId/compare` | Compare two backends (drift) |
|
|
45
|
+
| POST | `/stores/:storeId/backends/:backendId/publish` | Publish nx-content backend (push to remote) |
|
|
46
|
+
|
|
47
|
+
### Sync request body
|
|
48
|
+
|
|
49
|
+
```json
|
|
50
|
+
{
|
|
51
|
+
"sourceBackend": "mongo-main",
|
|
52
|
+
"targetBackend": "content-git",
|
|
53
|
+
"prefix": "functions/",
|
|
54
|
+
"namespace": "default",
|
|
55
|
+
"variant": null,
|
|
56
|
+
"mode": "copy-if-different",
|
|
57
|
+
"deleteExtraneous": false,
|
|
58
|
+
"dryRun": false
|
|
59
|
+
}
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
### Compare request body
|
|
63
|
+
|
|
64
|
+
```json
|
|
65
|
+
{
|
|
66
|
+
"sourceBackend": "mongo-main",
|
|
67
|
+
"targetBackend": "content-git",
|
|
68
|
+
"prefix": "functions/",
|
|
69
|
+
"namespace": "default",
|
|
70
|
+
"variant": null
|
|
71
|
+
}
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
Responses use a standard envelope: `{ ok: true, data: ... }` or `{ ok: false, error: string, code?: string }`.
|
|
75
|
+
|
|
76
|
+
## nx-content backend config
|
|
77
|
+
|
|
78
|
+
For backends with `type: 'nx-content'`, `BackendBindingRecord.config` supports:
|
|
79
|
+
|
|
80
|
+
- `mode`: `'dev' | 'prod'`
|
|
81
|
+
- `localRoot`: local content root path
|
|
82
|
+
- `repoUrl`, `branch`, `authToken`: git remote
|
|
83
|
+
- `variant`: optional variant
|
|
84
|
+
- `exportRoot`, `prefix`: optional path options
|
|
85
|
+
- `allowPush`: if `true`, publish is allowed (requires `repoUrl`)
|
|
86
|
+
|
|
87
|
+
## API (programmatic)
|
|
88
|
+
|
|
89
|
+
- **Adapters:** `BackendAdapterRegistry`, `NxContentBackendAdapter`, `createNxContentResolver`, mapping helpers
|
|
90
|
+
- **Services:** `SyncApplicationService`, `CompareApplicationService`, `PublishApplicationService`
|
|
91
|
+
- **Persistence:** `SyncRunPersistence` (implement or use `InMemorySyncRunPersistence`)
|
|
92
|
+
- **Validation:** `validateSyncInput`, `validateCompareInput`, `validateNxContentConfig`
|
|
93
|
+
- **Auth:** `requireSecretOrAdmin`, `getAuthFromRequest`
|
|
94
|
+
|
|
95
|
+
## Tests
|
|
96
|
+
|
|
97
|
+
```bash
|
|
98
|
+
npm test
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
## Publishing
|
|
102
|
+
|
|
103
|
+
For local development this package depends on `nx-content-store` via `file:../..`. To publish to npm, set the dependency to `"nx-content-store": "^1.0.0"` (or the minimum version you need) so that installs resolve it from the registry. Publish `nx-content-store` first, then `nx-content-store-server`.
|
|
104
|
+
|
|
105
|
+
## License
|
|
106
|
+
|
|
107
|
+
UNLICENSED
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { BackendBindingRecord } from 'nx-content-store';
|
|
2
|
+
import type { StoreBackendAdapter } from './types.js';
|
|
3
|
+
/**
|
|
4
|
+
* Registry that resolves a backend binding to the correct adapter implementation.
|
|
5
|
+
*/
|
|
6
|
+
export declare class BackendAdapterRegistry {
|
|
7
|
+
/**
|
|
8
|
+
* Return the adapter instance for the given binding.
|
|
9
|
+
* Supports: nx-content. Extensible for mongo compare adapter etc.
|
|
10
|
+
*/
|
|
11
|
+
getAdapter(binding: BackendBindingRecord): StoreBackendAdapter;
|
|
12
|
+
}
|
|
13
|
+
//# sourceMappingURL=BackendAdapterRegistry.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"BackendAdapterRegistry.d.ts","sourceRoot":"","sources":["../../src/adapters/BackendAdapterRegistry.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,kBAAkB,CAAC;AAC7D,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,YAAY,CAAC;AAItD;;GAEG;AACH,qBAAa,sBAAsB;IACjC;;;OAGG;IACH,UAAU,CAAC,OAAO,EAAE,oBAAoB,GAAG,mBAAmB;CAO/D"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { NxContentBackendAdapter } from './nxContent/NxContentBackendAdapter.js';
|
|
2
|
+
import { createNxContentResolver } from './nxContent/NxContentBindingFactory.js';
|
|
3
|
+
/**
|
|
4
|
+
* Registry that resolves a backend binding to the correct adapter implementation.
|
|
5
|
+
*/
|
|
6
|
+
export class BackendAdapterRegistry {
|
|
7
|
+
/**
|
|
8
|
+
* Return the adapter instance for the given binding.
|
|
9
|
+
* Supports: nx-content. Extensible for mongo compare adapter etc.
|
|
10
|
+
*/
|
|
11
|
+
getAdapter(binding) {
|
|
12
|
+
if (binding.type === 'nx-content') {
|
|
13
|
+
const resolver = createNxContentResolver(binding);
|
|
14
|
+
return new NxContentBackendAdapter(binding, resolver);
|
|
15
|
+
}
|
|
16
|
+
throw new Error(`Unsupported backend type: ${binding.type}`);
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
//# sourceMappingURL=BackendAdapterRegistry.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"BackendAdapterRegistry.js","sourceRoot":"","sources":["../../src/adapters/BackendAdapterRegistry.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,uBAAuB,EAAE,MAAM,wCAAwC,CAAC;AACjF,OAAO,EAAE,uBAAuB,EAAE,MAAM,wCAAwC,CAAC;AAEjF;;GAEG;AACH,MAAM,OAAO,sBAAsB;IACjC;;;OAGG;IACH,UAAU,CAAC,OAA6B;QACtC,IAAI,OAAO,CAAC,IAAI,KAAK,YAAY,EAAE,CAAC;YAClC,MAAM,QAAQ,GAAG,uBAAuB,CAAC,OAAO,CAAC,CAAC;YAClD,OAAO,IAAI,uBAAuB,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAC;QACxD,CAAC;QACD,MAAM,IAAI,KAAK,CAAC,6BAA8B,OAAgC,CAAC,IAAI,EAAE,CAAC,CAAC;IACzF,CAAC;CACF"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { ContentResolver } from 'nx-content';
|
|
2
|
+
import type { BackendBindingRecord } from 'nx-content-store';
|
|
3
|
+
import type { ContentSelector } from 'nx-content-store';
|
|
4
|
+
import type { StoreBackendAdapter, BackendWriteInput, BackendWriteResult, BackendListInput, BackendListResult, BackendRevisionSummary, BackendPublishResult } from '../types.js';
|
|
5
|
+
/**
|
|
6
|
+
* Adapter that bridges the store's StoreBackendAdapter interface to nx-content ContentResolver.
|
|
7
|
+
*/
|
|
8
|
+
export declare class NxContentBackendAdapter implements StoreBackendAdapter {
|
|
9
|
+
private readonly binding;
|
|
10
|
+
private readonly resolver;
|
|
11
|
+
constructor(binding: BackendBindingRecord, resolver: ContentResolver);
|
|
12
|
+
get(selector: ContentSelector): Promise<string>;
|
|
13
|
+
set(input: BackendWriteInput): Promise<BackendWriteResult>;
|
|
14
|
+
exists(selector: ContentSelector): Promise<boolean>;
|
|
15
|
+
list(input: BackendListInput): Promise<BackendListResult>;
|
|
16
|
+
private buildListPrefix;
|
|
17
|
+
listRevisions(selector: ContentSelector): Promise<BackendRevisionSummary[]>;
|
|
18
|
+
getRevision(selector: ContentSelector, ref: string): Promise<string>;
|
|
19
|
+
publish(): Promise<BackendPublishResult>;
|
|
20
|
+
}
|
|
21
|
+
//# sourceMappingURL=NxContentBackendAdapter.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"NxContentBackendAdapter.d.ts","sourceRoot":"","sources":["../../../src/adapters/nxContent/NxContentBackendAdapter.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAClD,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,kBAAkB,CAAC;AAC7D,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC;AACxD,OAAO,KAAK,EACV,mBAAmB,EACnB,iBAAiB,EACjB,kBAAkB,EAClB,gBAAgB,EAChB,iBAAiB,EACjB,sBAAsB,EACtB,oBAAoB,EACrB,MAAM,aAAa,CAAC;AASrB;;GAEG;AACH,qBAAa,uBAAwB,YAAW,mBAAmB;IAE/D,OAAO,CAAC,QAAQ,CAAC,OAAO;IACxB,OAAO,CAAC,QAAQ,CAAC,QAAQ;gBADR,OAAO,EAAE,oBAAoB,EAC7B,QAAQ,EAAE,eAAe;IAGtC,GAAG,CAAC,QAAQ,EAAE,eAAe,GAAG,OAAO,CAAC,MAAM,CAAC;IAK/C,GAAG,CAAC,KAAK,EAAE,iBAAiB,GAAG,OAAO,CAAC,kBAAkB,CAAC;IAQ1D,MAAM,CAAC,QAAQ,EAAE,eAAe,GAAG,OAAO,CAAC,OAAO,CAAC;IAOnD,IAAI,CAAC,KAAK,EAAE,gBAAgB,GAAG,OAAO,CAAC,iBAAiB,CAAC;IAkB/D,OAAO,CAAC,eAAe;IASjB,aAAa,CAAC,QAAQ,EAAE,eAAe,GAAG,OAAO,CAAC,sBAAsB,EAAE,CAAC;IAY3E,WAAW,CAAC,QAAQ,EAAE,eAAe,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAKpE,OAAO,IAAI,OAAO,CAAC,oBAAoB,CAAC;CAiB/C"}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { selectorToNxContentKey, nxContentKeyToSelector, stripExtensionFromKey, keyWithExtension, } from './mapping.js';
|
|
2
|
+
/**
|
|
3
|
+
* Adapter that bridges the store's StoreBackendAdapter interface to nx-content ContentResolver.
|
|
4
|
+
*/
|
|
5
|
+
export class NxContentBackendAdapter {
|
|
6
|
+
binding;
|
|
7
|
+
resolver;
|
|
8
|
+
constructor(binding, resolver) {
|
|
9
|
+
this.binding = binding;
|
|
10
|
+
this.resolver = resolver;
|
|
11
|
+
}
|
|
12
|
+
async get(selector) {
|
|
13
|
+
const key = selectorToNxContentKey(selector);
|
|
14
|
+
return this.resolver.get(key);
|
|
15
|
+
}
|
|
16
|
+
async set(input) {
|
|
17
|
+
const key = selectorToNxContentKey(input.selector);
|
|
18
|
+
const format = input.format ?? 'md';
|
|
19
|
+
const pathKey = keyWithExtension(key, format);
|
|
20
|
+
await this.resolver.set(pathKey, input.content);
|
|
21
|
+
return { key: pathKey, written: true };
|
|
22
|
+
}
|
|
23
|
+
async exists(selector) {
|
|
24
|
+
const key = selectorToNxContentKey(selector);
|
|
25
|
+
const mgr = this.resolver.getContentManager();
|
|
26
|
+
if (!mgr)
|
|
27
|
+
return false;
|
|
28
|
+
return mgr.exists(key);
|
|
29
|
+
}
|
|
30
|
+
async list(input) {
|
|
31
|
+
const prefix = this.buildListPrefix(input);
|
|
32
|
+
const rawKeys = await this.resolver.listKeys(prefix);
|
|
33
|
+
const keys = [];
|
|
34
|
+
for (const nxKey of rawKeys) {
|
|
35
|
+
const { key } = nxContentKeyToSelector(nxKey, input.storeId);
|
|
36
|
+
const canonical = stripExtensionFromKey(key);
|
|
37
|
+
if (!input.prefix || canonical.startsWith(input.prefix) || `${canonical}/`.startsWith(input.prefix)) {
|
|
38
|
+
keys.push(canonical);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
const total = keys.length;
|
|
42
|
+
const offset = input.offset ?? 0;
|
|
43
|
+
const limit = input.limit ?? total;
|
|
44
|
+
const sliced = keys.slice(offset, offset + limit);
|
|
45
|
+
return { keys: sliced, total };
|
|
46
|
+
}
|
|
47
|
+
buildListPrefix(input) {
|
|
48
|
+
const ns = input.namespace ?? 'default';
|
|
49
|
+
const base = input.variant ? `variants/${input.variant}/${ns}` : `${ns}`;
|
|
50
|
+
if (input.prefix) {
|
|
51
|
+
return `${base}/${input.prefix.replace(/^\/+/, '').replace(/\/+$/, '')}`;
|
|
52
|
+
}
|
|
53
|
+
return base;
|
|
54
|
+
}
|
|
55
|
+
async listRevisions(selector) {
|
|
56
|
+
const key = selectorToNxContentKey(selector);
|
|
57
|
+
const versions = await this.resolver.getVersions(key);
|
|
58
|
+
return versions.map((v) => ({
|
|
59
|
+
revisionId: v.sha,
|
|
60
|
+
ref: v.sha,
|
|
61
|
+
message: v.message,
|
|
62
|
+
createdAt: v.date,
|
|
63
|
+
author: v.author,
|
|
64
|
+
}));
|
|
65
|
+
}
|
|
66
|
+
async getRevision(selector, ref) {
|
|
67
|
+
const key = selectorToNxContentKey(selector);
|
|
68
|
+
return this.resolver.getAtRef(key, ref);
|
|
69
|
+
}
|
|
70
|
+
async publish() {
|
|
71
|
+
const config = this.binding.config;
|
|
72
|
+
if (!config?.allowPush) {
|
|
73
|
+
return { ok: false, message: 'Publish not allowed for this backend' };
|
|
74
|
+
}
|
|
75
|
+
try {
|
|
76
|
+
const result = await this.resolver.pushToRemote({});
|
|
77
|
+
return {
|
|
78
|
+
ok: result.pushed,
|
|
79
|
+
message: result.noChanges ? 'No changes to push' : result.commitHash ? 'Published' : 'Push completed',
|
|
80
|
+
ref: result.commitHash,
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
catch (err) {
|
|
84
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
85
|
+
return { ok: false, message };
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
//# sourceMappingURL=NxContentBackendAdapter.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"NxContentBackendAdapter.js","sourceRoot":"","sources":["../../../src/adapters/nxContent/NxContentBackendAdapter.ts"],"names":[],"mappings":"AAYA,OAAO,EACL,sBAAsB,EACtB,sBAAsB,EACtB,qBAAqB,EACrB,gBAAgB,GACjB,MAAM,cAAc,CAAC;AAGtB;;GAEG;AACH,MAAM,OAAO,uBAAuB;IAEf;IACA;IAFnB,YACmB,OAA6B,EAC7B,QAAyB;QADzB,YAAO,GAAP,OAAO,CAAsB;QAC7B,aAAQ,GAAR,QAAQ,CAAiB;IACzC,CAAC;IAEJ,KAAK,CAAC,GAAG,CAAC,QAAyB;QACjC,MAAM,GAAG,GAAG,sBAAsB,CAAC,QAAQ,CAAC,CAAC;QAC7C,OAAO,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;IAChC,CAAC;IAED,KAAK,CAAC,GAAG,CAAC,KAAwB;QAChC,MAAM,GAAG,GAAG,sBAAsB,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC;QACnD,MAAM,MAAM,GAAG,KAAK,CAAC,MAAM,IAAI,IAAI,CAAC;QACpC,MAAM,OAAO,GAAG,gBAAgB,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC;QAC9C,MAAM,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,OAAO,EAAE,KAAK,CAAC,OAAO,CAAC,CAAC;QAChD,OAAO,EAAE,GAAG,EAAE,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;IACzC,CAAC;IAED,KAAK,CAAC,MAAM,CAAC,QAAyB;QACpC,MAAM,GAAG,GAAG,sBAAsB,CAAC,QAAQ,CAAC,CAAC;QAC7C,MAAM,GAAG,GAAG,IAAI,CAAC,QAAQ,CAAC,iBAAiB,EAAE,CAAC;QAC9C,IAAI,CAAC,GAAG;YAAE,OAAO,KAAK,CAAC;QACvB,OAAO,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;IACzB,CAAC;IAED,KAAK,CAAC,IAAI,CAAC,KAAuB;QAChC,MAAM,MAAM,GAAG,IAAI,CAAC,eAAe,CAAC,KAAK,CAAC,CAAC;QAC3C,MAAM,OAAO,GAAG,MAAM,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;QACrD,MAAM,IAAI,GAAa,EAAE,CAAC;QAC1B,KAAK,MAAM,KAAK,IAAI,OAAO,EAAE,CAAC;YAC5B,MAAM,EAAE,GAAG,EAAE,GAAG,sBAAsB,CAAC,KAAK,EAAE,KAAK,CAAC,OAAO,CAAC,CAAC;YAC7D,MAAM,SAAS,GAAG,qBAAqB,CAAC,GAAG,CAAC,CAAC;YAC7C,IAAI,CAAC,KAAK,CAAC,MAAM,IAAI,SAAS,CAAC,UAAU,CAAC,KAAK,CAAC,MAAM,CAAC,IAAI,GAAG,SAAS,GAAG,CAAC,UAAU,CAAC,KAAK,CAAC,MAAM,CAAC,EAAE,CAAC;gBACpG,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;YACvB,CAAC;QACH,CAAC;QACD,MAAM,KAAK,GAAG,IAAI,CAAC,MAAM,CAAC;QAC1B,MAAM,MAAM,GAAG,KAAK,CAAC,MAAM,IAAI,CAAC,CAAC;QACjC,MAAM,KAAK,GAAG,KAAK,CAAC,KAAK,IAAI,KAAK,CAAC;QACnC,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,EAAE,MAAM,GAAG,KAAK,CAAC,CAAC;QAClD,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC;IACjC,CAAC;IAEO,eAAe,CAAC,KAAuB;QAC7C,MAAM,EAAE,GAAG,KAAK,CAAC,SAAS,IAAI,SAAS,CAAC;QACxC,MAAM,IAAI,GAAG,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,YAAY,KAAK,CAAC,OAAO,IAAI,EAAE,EAAE,CAAC,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC;QACzE,IAAI,KAAK,CAAC,MAAM,EAAE,CAAC;YACjB,OAAO,GAAG,IAAI,IAAI,KAAK,CAAC,MAAM,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,EAAE,CAAC;QAC3E,CAAC;QACD,OAAO,IAAI,CAAC;IACd,CAAC;IAED,KAAK,CAAC,aAAa,CAAC,QAAyB;QAC3C,MAAM,GAAG,GAAG,sBAAsB,CAAC,QAAQ,CAAC,CAAC;QAC7C,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,QAAQ,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC;QACtD,OAAO,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;YAC1B,UAAU,EAAE,CAAC,CAAC,GAAG;YACjB,GAAG,EAAE,CAAC,CAAC,GAAG;YACV,OAAO,EAAE,CAAC,CAAC,OAAO;YAClB,SAAS,EAAE,CAAC,CAAC,IAAI;YACjB,MAAM,EAAE,CAAC,CAAC,MAAM;SACjB,CAAC,CAAC,CAAC;IACN,CAAC;IAED,KAAK,CAAC,WAAW,CAAC,QAAyB,EAAE,GAAW;QACtD,MAAM,GAAG,GAAG,sBAAsB,CAAC,QAAQ,CAAC,CAAC;QAC7C,OAAO,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC;IAC1C,CAAC;IAED,KAAK,CAAC,OAAO;QACX,MAAM,MAAM,GAAG,IAAI,CAAC,OAAO,CAAC,MAA4C,CAAC;QACzE,IAAI,CAAC,MAAM,EAAE,SAAS,EAAE,CAAC;YACvB,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,OAAO,EAAE,sCAAsC,EAAE,CAAC;QACxE,CAAC;QACD,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,QAAQ,CAAC,YAAY,CAAC,EAAE,CAAC,CAAC;YACpD,OAAO;gBACL,EAAE,EAAE,MAAM,CAAC,MAAM;gBACjB,OAAO,EAAE,MAAM,CAAC,SAAS,CAAC,CAAC,CAAC,oBAAoB,CAAC,CAAC,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,gBAAgB;gBACrG,GAAG,EAAE,MAAM,CAAC,UAAU;aACvB,CAAC;QACJ,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,MAAM,OAAO,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;YACjE,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,OAAO,EAAE,CAAC;QAChC,CAAC;IACH,CAAC;CACF"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { ContentResolver } from 'nx-content';
|
|
2
|
+
import type { BackendBindingRecord } from 'nx-content-store';
|
|
3
|
+
/**
|
|
4
|
+
* Build an nx-content ContentResolver from backend binding config.
|
|
5
|
+
* Validates required config and maps to ContentManagerConfig.
|
|
6
|
+
*/
|
|
7
|
+
export declare function createNxContentResolver(binding: BackendBindingRecord): ContentResolver;
|
|
8
|
+
//# sourceMappingURL=NxContentBindingFactory.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"NxContentBindingFactory.d.ts","sourceRoot":"","sources":["../../../src/adapters/nxContent/NxContentBindingFactory.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAE7C,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,kBAAkB,CAAC;AAG7D;;;GAGG;AACH,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,oBAAoB,GAAG,eAAe,CAwBtF"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { ContentResolver } from 'nx-content';
|
|
2
|
+
/**
|
|
3
|
+
* Build an nx-content ContentResolver from backend binding config.
|
|
4
|
+
* Validates required config and maps to ContentManagerConfig.
|
|
5
|
+
*/
|
|
6
|
+
export function createNxContentResolver(binding) {
|
|
7
|
+
const config = binding.config;
|
|
8
|
+
if (!config) {
|
|
9
|
+
throw new Error('nx-content backend requires config');
|
|
10
|
+
}
|
|
11
|
+
if (config.allowPush && !config.repoUrl) {
|
|
12
|
+
throw new Error('allowPush requires repoUrl (and branch)');
|
|
13
|
+
}
|
|
14
|
+
const hasLocal = Boolean(config.localRoot);
|
|
15
|
+
const hasRemote = Boolean(config.repoUrl);
|
|
16
|
+
if (!hasLocal && !hasRemote) {
|
|
17
|
+
throw new Error('nx-content backend requires localRoot or repoUrl');
|
|
18
|
+
}
|
|
19
|
+
const managerConfig = {
|
|
20
|
+
mode: config.mode ?? 'dev',
|
|
21
|
+
localRoot: config.localRoot,
|
|
22
|
+
gitRepoUrl: config.repoUrl ?? null,
|
|
23
|
+
gitBranch: config.branch ?? 'main',
|
|
24
|
+
gitToken: config.authToken,
|
|
25
|
+
variant: config.variant ?? undefined,
|
|
26
|
+
};
|
|
27
|
+
return new ContentResolver(managerConfig);
|
|
28
|
+
}
|
|
29
|
+
//# sourceMappingURL=NxContentBindingFactory.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"NxContentBindingFactory.js","sourceRoot":"","sources":["../../../src/adapters/nxContent/NxContentBindingFactory.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAK7C;;;GAGG;AACH,MAAM,UAAU,uBAAuB,CAAC,OAA6B;IACnE,MAAM,MAAM,GAAG,OAAO,CAAC,MAA4C,CAAC;IACpE,IAAI,CAAC,MAAM,EAAE,CAAC;QACZ,MAAM,IAAI,KAAK,CAAC,oCAAoC,CAAC,CAAC;IACxD,CAAC;IACD,IAAI,MAAM,CAAC,SAAS,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;QACxC,MAAM,IAAI,KAAK,CAAC,yCAAyC,CAAC,CAAC;IAC7D,CAAC;IACD,MAAM,QAAQ,GAAG,OAAO,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC;IAC3C,MAAM,SAAS,GAAG,OAAO,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;IAC1C,IAAI,CAAC,QAAQ,IAAI,CAAC,SAAS,EAAE,CAAC;QAC5B,MAAM,IAAI,KAAK,CAAC,kDAAkD,CAAC,CAAC;IACtE,CAAC;IAED,MAAM,aAAa,GAAyB;QAC1C,IAAI,EAAE,MAAM,CAAC,IAAI,IAAI,KAAK;QAC1B,SAAS,EAAE,MAAM,CAAC,SAAS;QAC3B,UAAU,EAAE,MAAM,CAAC,OAAO,IAAI,IAAI;QAClC,SAAS,EAAE,MAAM,CAAC,MAAM,IAAI,MAAM;QAClC,QAAQ,EAAE,MAAM,CAAC,SAAS;QAC1B,OAAO,EAAE,MAAM,CAAC,OAAO,IAAI,SAAS;KACrC,CAAC;IAEF,OAAO,IAAI,eAAe,CAAC,aAAa,CAAC,CAAC;AAC5C,CAAC"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { StoreBackendAdapter } from '../types.js';
|
|
2
|
+
import type { CompareBackendsResult } from 'nx-content-store';
|
|
3
|
+
export interface CompareAdaptersInput {
|
|
4
|
+
sourceBackend: string;
|
|
5
|
+
targetBackend: string;
|
|
6
|
+
prefix?: string;
|
|
7
|
+
namespace?: string;
|
|
8
|
+
variant?: string | null;
|
|
9
|
+
sourceAdapter: StoreBackendAdapter;
|
|
10
|
+
targetAdapter: StoreBackendAdapter;
|
|
11
|
+
storeId: string;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Compare two backends by normalized content hash.
|
|
15
|
+
* Returns onlyInSource, onlyInTarget, and different (by hash).
|
|
16
|
+
*/
|
|
17
|
+
export declare function compareAdapters(input: CompareAdaptersInput): Promise<CompareBackendsResult>;
|
|
18
|
+
//# sourceMappingURL=compare.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"compare.d.ts","sourceRoot":"","sources":["../../../src/adapters/nxContent/compare.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AACvD,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,kBAAkB,CAAC;AAM9D,MAAM,WAAW,oBAAoB;IACnC,aAAa,EAAE,MAAM,CAAC;IACtB,aAAa,EAAE,MAAM,CAAC;IACtB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,aAAa,EAAE,mBAAmB,CAAC;IACnC,aAAa,EAAE,mBAAmB,CAAC;IACnC,OAAO,EAAE,MAAM,CAAC;CACjB;AAED;;;GAGG;AACH,wBAAsB,eAAe,CAAC,KAAK,EAAE,oBAAoB,GAAG,OAAO,CAAC,qBAAqB,CAAC,CAiEjG"}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
function simpleHash(text) {
|
|
3
|
+
return createHash('sha256').update(text, 'utf8').digest('hex').slice(0, 16);
|
|
4
|
+
}
|
|
5
|
+
/**
|
|
6
|
+
* Compare two backends by normalized content hash.
|
|
7
|
+
* Returns onlyInSource, onlyInTarget, and different (by hash).
|
|
8
|
+
*/
|
|
9
|
+
export async function compareAdapters(input) {
|
|
10
|
+
const listInput = {
|
|
11
|
+
storeId: input.storeId,
|
|
12
|
+
namespace: input.namespace ?? 'default',
|
|
13
|
+
prefix: input.prefix,
|
|
14
|
+
variant: input.variant ?? null,
|
|
15
|
+
};
|
|
16
|
+
const [sourceList, targetList] = await Promise.all([
|
|
17
|
+
input.sourceAdapter.list(listInput),
|
|
18
|
+
input.targetAdapter.list(listInput),
|
|
19
|
+
]);
|
|
20
|
+
const sourceKeys = new Set(sourceList.keys);
|
|
21
|
+
const targetKeys = new Set(targetList.keys);
|
|
22
|
+
const onlyInSource = [];
|
|
23
|
+
const onlyInTarget = [];
|
|
24
|
+
const different = [];
|
|
25
|
+
for (const k of sourceKeys) {
|
|
26
|
+
if (!targetKeys.has(k)) {
|
|
27
|
+
onlyInSource.push(k);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
for (const k of targetKeys) {
|
|
31
|
+
if (!sourceKeys.has(k)) {
|
|
32
|
+
onlyInTarget.push(k);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
const common = [...sourceKeys].filter((k) => targetKeys.has(k));
|
|
36
|
+
for (const key of common) {
|
|
37
|
+
const selector = {
|
|
38
|
+
storeId: input.storeId,
|
|
39
|
+
namespace: input.namespace ?? 'default',
|
|
40
|
+
key,
|
|
41
|
+
variant: input.variant ?? null,
|
|
42
|
+
};
|
|
43
|
+
let sourceHash;
|
|
44
|
+
let targetHash;
|
|
45
|
+
try {
|
|
46
|
+
const srcContent = await input.sourceAdapter.get(selector);
|
|
47
|
+
sourceHash = simpleHash(srcContent);
|
|
48
|
+
}
|
|
49
|
+
catch {
|
|
50
|
+
sourceHash = undefined;
|
|
51
|
+
}
|
|
52
|
+
try {
|
|
53
|
+
const tgtContent = await input.targetAdapter.get(selector);
|
|
54
|
+
targetHash = simpleHash(tgtContent);
|
|
55
|
+
}
|
|
56
|
+
catch {
|
|
57
|
+
targetHash = undefined;
|
|
58
|
+
}
|
|
59
|
+
if (sourceHash !== targetHash) {
|
|
60
|
+
different.push({ key, sourceHash, targetHash });
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
return {
|
|
64
|
+
sourceBackend: input.sourceBackend,
|
|
65
|
+
targetBackend: input.targetBackend,
|
|
66
|
+
onlyInSource,
|
|
67
|
+
onlyInTarget,
|
|
68
|
+
different,
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
//# sourceMappingURL=compare.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"compare.js","sourceRoot":"","sources":["../../../src/adapters/nxContent/compare.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { ManagedContentStore } from 'nx-content-store';
|
|
2
|
+
import type { RequestContext } from 'nx-content-store';
|
|
3
|
+
import type { StoreBackendAdapter } from '../types.js';
|
|
4
|
+
export type SyncMode = 'copy-all' | 'copy-if-missing' | 'copy-if-different';
|
|
5
|
+
export interface ImportFromNxContentInput {
|
|
6
|
+
storeId: string;
|
|
7
|
+
namespace?: string;
|
|
8
|
+
prefix?: string;
|
|
9
|
+
variant?: string | null;
|
|
10
|
+
mode: SyncMode;
|
|
11
|
+
dryRun?: boolean;
|
|
12
|
+
}
|
|
13
|
+
export interface ExportToNxContentInput {
|
|
14
|
+
storeId: string;
|
|
15
|
+
namespace?: string;
|
|
16
|
+
prefix?: string;
|
|
17
|
+
variant?: string | null;
|
|
18
|
+
mode: SyncMode;
|
|
19
|
+
dryRun?: boolean;
|
|
20
|
+
}
|
|
21
|
+
export interface ImportExportStats {
|
|
22
|
+
scanned: number;
|
|
23
|
+
copied: number;
|
|
24
|
+
skipped: number;
|
|
25
|
+
deleted: number;
|
|
26
|
+
errors: number;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Import content from nx-content adapter into the authoritative store.
|
|
30
|
+
*/
|
|
31
|
+
export declare function importFromNxContent(store: ManagedContentStore, adapter: StoreBackendAdapter, input: ImportFromNxContentInput, ctx?: RequestContext): Promise<ImportExportStats>;
|
|
32
|
+
/**
|
|
33
|
+
* Export content from authoritative store into nx-content adapter.
|
|
34
|
+
*/
|
|
35
|
+
export declare function exportToNxContent(store: ManagedContentStore, adapter: StoreBackendAdapter, input: ExportToNxContentInput, _ctx?: RequestContext): Promise<ImportExportStats>;
|
|
36
|
+
//# sourceMappingURL=importExport.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"importExport.d.ts","sourceRoot":"","sources":["../../../src/adapters/nxContent/importExport.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,kBAAkB,CAAC;AAC5D,OAAO,KAAK,EAAmB,cAAc,EAAE,MAAM,kBAAkB,CAAC;AACxE,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AAGvD,MAAM,MAAM,QAAQ,GAAG,UAAU,GAAG,iBAAiB,GAAG,mBAAmB,CAAC;AAE5E,MAAM,WAAW,wBAAwB;IACvC,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,IAAI,EAAE,QAAQ,CAAC;IACf,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB;AAED,MAAM,WAAW,sBAAsB;IACrC,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,IAAI,EAAE,QAAQ,CAAC;IACf,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB;AAED,MAAM,WAAW,iBAAiB;IAChC,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;CAChB;AAED;;GAEG;AACH,wBAAsB,mBAAmB,CACvC,KAAK,EAAE,mBAAmB,EAC1B,OAAO,EAAE,mBAAmB,EAC5B,KAAK,EAAE,wBAAwB,EAC/B,GAAG,CAAC,EAAE,cAAc,GACnB,OAAO,CAAC,iBAAiB,CAAC,CAgD5B;AAED;;GAEG;AACH,wBAAsB,iBAAiB,CACrC,KAAK,EAAE,mBAAmB,EAC1B,OAAO,EAAE,mBAAmB,EAC5B,KAAK,EAAE,sBAAsB,EAC7B,IAAI,CAAC,EAAE,cAAc,GACpB,OAAO,CAAC,iBAAiB,CAAC,CAiE5B"}
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
/**
|
|
3
|
+
* Import content from nx-content adapter into the authoritative store.
|
|
4
|
+
*/
|
|
5
|
+
export async function importFromNxContent(store, adapter, input, ctx) {
|
|
6
|
+
const stats = { scanned: 0, copied: 0, skipped: 0, deleted: 0, errors: 0 };
|
|
7
|
+
const listResult = await adapter.list({
|
|
8
|
+
storeId: input.storeId,
|
|
9
|
+
namespace: input.namespace,
|
|
10
|
+
prefix: input.prefix,
|
|
11
|
+
variant: input.variant,
|
|
12
|
+
});
|
|
13
|
+
const keys = listResult.keys;
|
|
14
|
+
for (const key of keys) {
|
|
15
|
+
stats.scanned += 1;
|
|
16
|
+
const selector = {
|
|
17
|
+
storeId: input.storeId,
|
|
18
|
+
namespace: input.namespace ?? 'default',
|
|
19
|
+
key,
|
|
20
|
+
variant: input.variant ?? null,
|
|
21
|
+
};
|
|
22
|
+
try {
|
|
23
|
+
const content = await adapter.get(selector);
|
|
24
|
+
const existing = await store.content.getEntry(selector);
|
|
25
|
+
const hash = simpleHash(content);
|
|
26
|
+
if (input.dryRun) {
|
|
27
|
+
if (!existing)
|
|
28
|
+
stats.copied += 1;
|
|
29
|
+
else if (input.mode === 'copy-if-different' && existing.hash !== hash)
|
|
30
|
+
stats.copied += 1;
|
|
31
|
+
else
|
|
32
|
+
stats.skipped += 1;
|
|
33
|
+
continue;
|
|
34
|
+
}
|
|
35
|
+
if (input.mode === 'copy-if-missing' && existing) {
|
|
36
|
+
stats.skipped += 1;
|
|
37
|
+
continue;
|
|
38
|
+
}
|
|
39
|
+
if (input.mode === 'copy-if-different' && existing && existing.hash === hash) {
|
|
40
|
+
stats.skipped += 1;
|
|
41
|
+
continue;
|
|
42
|
+
}
|
|
43
|
+
await store.content.set(selector, { content, format: 'md', noOpIfSameHash: true }, ctx);
|
|
44
|
+
stats.copied += 1;
|
|
45
|
+
}
|
|
46
|
+
catch (err) {
|
|
47
|
+
stats.errors += 1;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
return stats;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Export content from authoritative store into nx-content adapter.
|
|
54
|
+
*/
|
|
55
|
+
export async function exportToNxContent(store, adapter, input, _ctx) {
|
|
56
|
+
const stats = { scanned: 0, copied: 0, skipped: 0, deleted: 0, errors: 0 };
|
|
57
|
+
const keys = await store.content.listKeys({
|
|
58
|
+
storeId: input.storeId,
|
|
59
|
+
namespace: input.namespace,
|
|
60
|
+
prefix: input.prefix,
|
|
61
|
+
});
|
|
62
|
+
for (const key of keys) {
|
|
63
|
+
stats.scanned += 1;
|
|
64
|
+
const selector = {
|
|
65
|
+
storeId: input.storeId,
|
|
66
|
+
namespace: input.namespace ?? 'default',
|
|
67
|
+
key,
|
|
68
|
+
variant: input.variant ?? null,
|
|
69
|
+
};
|
|
70
|
+
try {
|
|
71
|
+
const entry = await store.content.getEntry(selector);
|
|
72
|
+
if (!entry) {
|
|
73
|
+
stats.skipped += 1;
|
|
74
|
+
continue;
|
|
75
|
+
}
|
|
76
|
+
if (input.dryRun) {
|
|
77
|
+
const exists = await adapter.exists(selector);
|
|
78
|
+
if (!exists)
|
|
79
|
+
stats.copied += 1;
|
|
80
|
+
else if (input.mode === 'copy-if-different') {
|
|
81
|
+
const remote = await adapter.get(selector);
|
|
82
|
+
if (simpleHash(remote) !== entry.hash)
|
|
83
|
+
stats.copied += 1;
|
|
84
|
+
else
|
|
85
|
+
stats.skipped += 1;
|
|
86
|
+
}
|
|
87
|
+
else
|
|
88
|
+
stats.skipped += 1;
|
|
89
|
+
continue;
|
|
90
|
+
}
|
|
91
|
+
if (input.mode === 'copy-if-missing') {
|
|
92
|
+
const exists = await adapter.exists(selector);
|
|
93
|
+
if (exists) {
|
|
94
|
+
stats.skipped += 1;
|
|
95
|
+
continue;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
if (input.mode === 'copy-if-different') {
|
|
99
|
+
const exists = await adapter.exists(selector);
|
|
100
|
+
if (exists) {
|
|
101
|
+
const remote = await adapter.get(selector);
|
|
102
|
+
if (simpleHash(remote) === entry.hash) {
|
|
103
|
+
stats.skipped += 1;
|
|
104
|
+
continue;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
await adapter.set({
|
|
109
|
+
selector,
|
|
110
|
+
content: entry.text,
|
|
111
|
+
format: entry.format,
|
|
112
|
+
});
|
|
113
|
+
stats.copied += 1;
|
|
114
|
+
}
|
|
115
|
+
catch (err) {
|
|
116
|
+
stats.errors += 1;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
return stats;
|
|
120
|
+
}
|
|
121
|
+
function simpleHash(text) {
|
|
122
|
+
return createHash('sha256').update(text, 'utf8').digest('hex').slice(0, 16);
|
|
123
|
+
}
|
|
124
|
+
//# sourceMappingURL=importExport.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"importExport.js","sourceRoot":"","sources":["../../../src/adapters/nxContent/importExport.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { ContentSelector } from 'nx-content-store';
|
|
2
|
+
import type { ContentFormat } from 'nx-content-store';
|
|
3
|
+
/**
|
|
4
|
+
* Build nx-content key from store selector.
|
|
5
|
+
* Namespace is always included in the key path.
|
|
6
|
+
* Example: { namespace: "default", key: "blocks/greeting" } -> "default/blocks/greeting"
|
|
7
|
+
*/
|
|
8
|
+
export declare function selectorToNxContentKey(selector: ContentSelector): string;
|
|
9
|
+
/**
|
|
10
|
+
* Parse nx-content key into store selector parts (namespace, key).
|
|
11
|
+
* Variant is inferred if key starts with variants/<variant>/.
|
|
12
|
+
*/
|
|
13
|
+
export declare function nxContentKeyToSelector(nxKey: string, storeId: string): {
|
|
14
|
+
storeId: string;
|
|
15
|
+
namespace: string;
|
|
16
|
+
key: string;
|
|
17
|
+
variant: string | null;
|
|
18
|
+
};
|
|
19
|
+
/**
|
|
20
|
+
* Get file extension for format (e.g. md -> .md).
|
|
21
|
+
* Raw has no extension.
|
|
22
|
+
*/
|
|
23
|
+
export declare function formatToExtension(format: ContentFormat): string;
|
|
24
|
+
/**
|
|
25
|
+
* Infer format from file path/extension.
|
|
26
|
+
*/
|
|
27
|
+
export declare function extensionToFormat(pathOrKey: string): ContentFormat;
|
|
28
|
+
/**
|
|
29
|
+
* Strip known format extension from nx-content key for canonical key.
|
|
30
|
+
*/
|
|
31
|
+
export declare function stripExtensionFromKey(key: string): string;
|
|
32
|
+
/**
|
|
33
|
+
* Append extension to nx-content key for physical path.
|
|
34
|
+
*/
|
|
35
|
+
export declare function keyWithExtension(key: string, format: ContentFormat): string;
|
|
36
|
+
//# sourceMappingURL=mapping.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mapping.d.ts","sourceRoot":"","sources":["../../../src/adapters/nxContent/mapping.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC;AACxD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AAiBtD;;;;GAIG;AACH,wBAAgB,sBAAsB,CAAC,QAAQ,EAAE,eAAe,GAAG,MAAM,CAOxE;AAED;;;GAGG;AACH,wBAAgB,sBAAsB,CACpC,KAAK,EAAE,MAAM,EACb,OAAO,EAAE,MAAM,GACd;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,CAgB7E;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,aAAa,GAAG,MAAM,CAE/D;AAED;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,SAAS,EAAE,MAAM,GAAG,aAAa,CAQlE;AAED;;GAEG;AACH,wBAAgB,qBAAqB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAQzD;AAED;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,aAAa,GAAG,MAAM,CAG3E"}
|