oc 0.50.55 → 0.50.56
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/.turbo/turbo-build.log +4 -4
- package/.turbo/turbo-test-silent.log +11 -11
- package/.turbo/turbo-test.log +6 -1614
- package/CHANGELOG.md +8 -0
- package/README.md +180 -0
- package/dist/cli/commands.d.ts +8 -0
- package/dist/cli/commands.js +8 -0
- package/dist/cli/facade/dev.d.ts +2 -2
- package/dist/cli/facade/registry-migrate-metadata.d.ts +20 -0
- package/dist/cli/facade/registry-migrate-metadata.js +105 -0
- package/dist/cli/index.js +2 -0
- package/dist/components/oc-client/_package/package.json +4 -4
- package/dist/components/oc-client/_package/server.js +1 -1
- package/dist/components/oc-client/package.json +1 -1
- package/dist/registry/domain/components-cache/index.d.ts +3 -1
- package/dist/registry/domain/components-cache/index.js +28 -5
- package/dist/registry/domain/components-details.d.ts +3 -1
- package/dist/registry/domain/components-details.js +26 -5
- package/dist/registry/domain/metadata-adapter-options.d.ts +6 -0
- package/dist/registry/domain/metadata-adapter-options.js +12 -0
- package/dist/registry/domain/metadata-index.d.ts +15 -0
- package/dist/registry/domain/metadata-index.js +132 -0
- package/dist/registry/domain/metadata-migration.d.ts +38 -0
- package/dist/registry/domain/metadata-migration.js +128 -0
- package/dist/registry/domain/repository.d.ts +1 -0
- package/dist/registry/domain/repository.js +106 -7
- package/dist/registry/domain/validators/registry-configuration.js +21 -0
- package/dist/registry/index.d.ts +1 -1
- package/dist/registry/index.js +6 -2
- package/dist/resources/index.d.ts +3 -0
- package/dist/resources/index.js +3 -0
- package/dist/types.d.ts +41 -1
- package/package.json +3 -2
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.createMetadataIndex = exports.getComponentRow = exports.getComponentsDetailsFromRows = exports.getComponentsListFromRows = void 0;
|
|
7
|
+
const semver_1 = __importDefault(require("semver"));
|
|
8
|
+
// Derive `lastEdit` from the data itself (the most recent publish) rather than
|
|
9
|
+
// the wall-clock time the snapshot happened to be built. This keeps the value
|
|
10
|
+
// semantically meaningful (it only advances when something is actually
|
|
11
|
+
// published) so the poll's `data.lastEdit > cached.lastEdit` guard works and
|
|
12
|
+
// the exported legacy `components.json` reflects the real last edit.
|
|
13
|
+
const getLastEditFromRows = (rows) => rows.reduce((max, row) => (row.publishDate > max ? row.publishDate : max), 0);
|
|
14
|
+
const getComponentsListFromRows = (rows) => {
|
|
15
|
+
const components = {};
|
|
16
|
+
for (const row of rows) {
|
|
17
|
+
const versions = components[row.name] || [];
|
|
18
|
+
versions.push(row.version);
|
|
19
|
+
components[row.name] = versions;
|
|
20
|
+
}
|
|
21
|
+
for (const versions of Object.values(components)) {
|
|
22
|
+
versions.sort(semver_1.default.compare);
|
|
23
|
+
}
|
|
24
|
+
return {
|
|
25
|
+
lastEdit: getLastEditFromRows(rows),
|
|
26
|
+
components
|
|
27
|
+
};
|
|
28
|
+
};
|
|
29
|
+
exports.getComponentsListFromRows = getComponentsListFromRows;
|
|
30
|
+
const getComponentsDetailsFromRows = (rows) => {
|
|
31
|
+
const components = {};
|
|
32
|
+
for (const row of rows) {
|
|
33
|
+
const componentDetails = components[row.name] || {};
|
|
34
|
+
componentDetails[row.version] = { publishDate: row.publishDate };
|
|
35
|
+
if (row.templateSize !== undefined) {
|
|
36
|
+
componentDetails[row.version].templateSize = row.templateSize;
|
|
37
|
+
}
|
|
38
|
+
components[row.name] = componentDetails;
|
|
39
|
+
}
|
|
40
|
+
return {
|
|
41
|
+
lastEdit: getLastEditFromRows(rows),
|
|
42
|
+
components
|
|
43
|
+
};
|
|
44
|
+
};
|
|
45
|
+
exports.getComponentsDetailsFromRows = getComponentsDetailsFromRows;
|
|
46
|
+
const getComponentRow = (name, version, component) => ({
|
|
47
|
+
name,
|
|
48
|
+
version,
|
|
49
|
+
publishDate: component.oc.date || 0,
|
|
50
|
+
templateSize: component.oc.files.template.size
|
|
51
|
+
});
|
|
52
|
+
exports.getComponentRow = getComponentRow;
|
|
53
|
+
const FORCED_REFRESH_INTERVAL_MS = 5 * 60 * 1000;
|
|
54
|
+
const buildSnapshot = (rows) => ({
|
|
55
|
+
componentsList: (0, exports.getComponentsListFromRows)(rows),
|
|
56
|
+
componentsDetails: (0, exports.getComponentsDetailsFromRows)(rows)
|
|
57
|
+
});
|
|
58
|
+
const createMetadataIndex = (metadataStore) => {
|
|
59
|
+
let snapshot;
|
|
60
|
+
let lastChangeToken;
|
|
61
|
+
let lastForcedRefreshAt = 0;
|
|
62
|
+
let localAddGeneration = 0;
|
|
63
|
+
const refresh = async () => {
|
|
64
|
+
const refreshGeneration = localAddGeneration;
|
|
65
|
+
const changeToken = metadataStore.getChangeToken
|
|
66
|
+
? await metadataStore.getChangeToken()
|
|
67
|
+
: undefined;
|
|
68
|
+
const now = Date.now();
|
|
69
|
+
if (snapshot &&
|
|
70
|
+
changeToken !== undefined &&
|
|
71
|
+
changeToken === lastChangeToken &&
|
|
72
|
+
now - lastForcedRefreshAt < FORCED_REFRESH_INTERVAL_MS) {
|
|
73
|
+
return snapshot;
|
|
74
|
+
}
|
|
75
|
+
const rows = await metadataStore.getAllComponents();
|
|
76
|
+
if (snapshot && refreshGeneration !== localAddGeneration) {
|
|
77
|
+
return snapshot;
|
|
78
|
+
}
|
|
79
|
+
snapshot = buildSnapshot(rows);
|
|
80
|
+
lastChangeToken = changeToken;
|
|
81
|
+
lastForcedRefreshAt = now;
|
|
82
|
+
return snapshot;
|
|
83
|
+
};
|
|
84
|
+
const add = (row) => {
|
|
85
|
+
// No snapshot yet: build one from the single row.
|
|
86
|
+
if (!snapshot) {
|
|
87
|
+
snapshot = buildSnapshot([row]);
|
|
88
|
+
localAddGeneration += 1;
|
|
89
|
+
return snapshot;
|
|
90
|
+
}
|
|
91
|
+
const { name, version } = row;
|
|
92
|
+
const prevList = snapshot.componentsList.components;
|
|
93
|
+
const prevDetails = snapshot.componentsDetails.components;
|
|
94
|
+
// Already present: keep the snapshot unchanged (mirrors the unique-constraint
|
|
95
|
+
// guarantee, so a duplicate publish doesn't disturb the cache).
|
|
96
|
+
if (prevDetails[name]?.[version]) {
|
|
97
|
+
return snapshot;
|
|
98
|
+
}
|
|
99
|
+
// Only the published component is rebuilt; every other component entry is
|
|
100
|
+
// shared by reference, so the cost is O(versions-of-this-component) rather
|
|
101
|
+
// than O(registry). New container objects are produced (rather than mutating
|
|
102
|
+
// in place) so in-flight readers keep a consistent view of the old snapshot.
|
|
103
|
+
const versions = [...(prevList[name] ?? []), version].sort(semver_1.default.compare);
|
|
104
|
+
const detail = {
|
|
105
|
+
...(prevDetails[name] ?? {}),
|
|
106
|
+
[version]: { publishDate: row.publishDate }
|
|
107
|
+
};
|
|
108
|
+
if (row.templateSize !== undefined) {
|
|
109
|
+
detail[version].templateSize = row.templateSize;
|
|
110
|
+
}
|
|
111
|
+
const lastEdit = Math.max(snapshot.componentsList.lastEdit, row.publishDate);
|
|
112
|
+
snapshot = {
|
|
113
|
+
componentsList: {
|
|
114
|
+
lastEdit,
|
|
115
|
+
components: { ...prevList, [name]: versions }
|
|
116
|
+
},
|
|
117
|
+
componentsDetails: {
|
|
118
|
+
lastEdit,
|
|
119
|
+
components: { ...prevDetails, [name]: detail }
|
|
120
|
+
}
|
|
121
|
+
};
|
|
122
|
+
localAddGeneration += 1;
|
|
123
|
+
return snapshot;
|
|
124
|
+
};
|
|
125
|
+
return {
|
|
126
|
+
get: () => snapshot,
|
|
127
|
+
add,
|
|
128
|
+
refresh,
|
|
129
|
+
getOrRefresh: () => (snapshot ? Promise.resolve(snapshot) : refresh())
|
|
130
|
+
};
|
|
131
|
+
};
|
|
132
|
+
exports.createMetadataIndex = createMetadataIndex;
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import type { StorageAdapter } from 'oc-storage-adapters-utils';
|
|
2
|
+
import type { ComponentRow, ComponentsDetails, MetadataStore } from '../../types';
|
|
3
|
+
export interface MetadataMigrationResult {
|
|
4
|
+
scanned: number;
|
|
5
|
+
inserted: number;
|
|
6
|
+
skipped: number;
|
|
7
|
+
}
|
|
8
|
+
export interface MetadataExportResult {
|
|
9
|
+
exported: number;
|
|
10
|
+
files: string[];
|
|
11
|
+
}
|
|
12
|
+
export declare const getComponentRowsFromComponentsDetails: (componentsDetails: ComponentsDetails) => ComponentRow[];
|
|
13
|
+
export declare const backfillMetadataRows: (metadataStore: MetadataStore, rows: ComponentRow[]) => Promise<MetadataMigrationResult>;
|
|
14
|
+
export declare const backfillMetadataFromComponentsDetails: (metadataStore: MetadataStore, componentsDetails: ComponentsDetails) => Promise<MetadataMigrationResult>;
|
|
15
|
+
export declare const getComponentRowsFromStorageDirectories: (options: {
|
|
16
|
+
cdn: StorageAdapter;
|
|
17
|
+
componentsDir: string;
|
|
18
|
+
}) => Promise<ComponentRow[]>;
|
|
19
|
+
export declare const backfillMetadataFromStorageDirectories: (options: {
|
|
20
|
+
metadataStore: MetadataStore;
|
|
21
|
+
cdn: StorageAdapter;
|
|
22
|
+
componentsDir: string;
|
|
23
|
+
}) => Promise<MetadataMigrationResult>;
|
|
24
|
+
export declare const reconcileMetadataFromStorage: (options: {
|
|
25
|
+
metadataStore: MetadataStore;
|
|
26
|
+
cdn: StorageAdapter;
|
|
27
|
+
componentsDir: string;
|
|
28
|
+
}) => Promise<MetadataMigrationResult>;
|
|
29
|
+
export declare const exportLegacyMetadataFiles: (options: {
|
|
30
|
+
metadataStore: MetadataStore;
|
|
31
|
+
cdn: StorageAdapter;
|
|
32
|
+
componentsDir: string;
|
|
33
|
+
}) => Promise<MetadataExportResult>;
|
|
34
|
+
export declare const backfillMetadataFromStorageDetails: (options: {
|
|
35
|
+
metadataStore: MetadataStore;
|
|
36
|
+
cdn: StorageAdapter;
|
|
37
|
+
componentsDir: string;
|
|
38
|
+
}) => Promise<MetadataMigrationResult>;
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.backfillMetadataFromStorageDetails = exports.exportLegacyMetadataFiles = exports.reconcileMetadataFromStorage = exports.backfillMetadataFromStorageDirectories = exports.getComponentRowsFromStorageDirectories = exports.backfillMetadataFromComponentsDetails = exports.backfillMetadataRows = exports.getComponentRowsFromComponentsDetails = void 0;
|
|
7
|
+
const oc_metadata_adapters_utils_1 = require("oc-metadata-adapters-utils");
|
|
8
|
+
const pLimit_1 = __importDefault(require("../../utils/pLimit"));
|
|
9
|
+
const metadata_index_1 = require("./metadata-index");
|
|
10
|
+
const isNotFoundError = (err, code) => err === code || err?.code === code;
|
|
11
|
+
const getTemplateSize = (component) => component.oc.files.template.size;
|
|
12
|
+
const getComponentRowsFromComponentsDetails = (componentsDetails) => {
|
|
13
|
+
const rows = [];
|
|
14
|
+
for (const [name, versions] of Object.entries(componentsDetails.components || {})) {
|
|
15
|
+
for (const [version, details] of Object.entries(versions)) {
|
|
16
|
+
const row = {
|
|
17
|
+
name,
|
|
18
|
+
version,
|
|
19
|
+
publishDate: details.publishDate
|
|
20
|
+
};
|
|
21
|
+
if (details.templateSize !== undefined) {
|
|
22
|
+
row.templateSize = details.templateSize;
|
|
23
|
+
}
|
|
24
|
+
rows.push(row);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
return rows;
|
|
28
|
+
};
|
|
29
|
+
exports.getComponentRowsFromComponentsDetails = getComponentRowsFromComponentsDetails;
|
|
30
|
+
const backfillMetadataRows = async (metadataStore, rows) => {
|
|
31
|
+
const result = {
|
|
32
|
+
scanned: rows.length,
|
|
33
|
+
inserted: 0,
|
|
34
|
+
skipped: 0
|
|
35
|
+
};
|
|
36
|
+
const limit = (0, pLimit_1.default)(10);
|
|
37
|
+
await Promise.all(rows.map((row) => limit(async () => {
|
|
38
|
+
try {
|
|
39
|
+
await metadataStore.addVersion(row);
|
|
40
|
+
result.inserted += 1;
|
|
41
|
+
}
|
|
42
|
+
catch (err) {
|
|
43
|
+
if (err?.code === oc_metadata_adapters_utils_1.VERSION_ALREADY_EXISTS ||
|
|
44
|
+
err?.code === oc_metadata_adapters_utils_1.VERSION_PUBLISH_IN_PROGRESS) {
|
|
45
|
+
result.skipped += 1;
|
|
46
|
+
}
|
|
47
|
+
else {
|
|
48
|
+
throw err;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
})));
|
|
52
|
+
return result;
|
|
53
|
+
};
|
|
54
|
+
exports.backfillMetadataRows = backfillMetadataRows;
|
|
55
|
+
const backfillMetadataFromComponentsDetails = async (metadataStore, componentsDetails) => (0, exports.backfillMetadataRows)(metadataStore, (0, exports.getComponentRowsFromComponentsDetails)(componentsDetails));
|
|
56
|
+
exports.backfillMetadataFromComponentsDetails = backfillMetadataFromComponentsDetails;
|
|
57
|
+
const getComponentRowsFromStorageDirectories = async (options) => {
|
|
58
|
+
let components;
|
|
59
|
+
try {
|
|
60
|
+
components = await options.cdn.listSubDirectories(options.componentsDir);
|
|
61
|
+
}
|
|
62
|
+
catch (err) {
|
|
63
|
+
if (isNotFoundError(err, 'dir_not_found')) {
|
|
64
|
+
return [];
|
|
65
|
+
}
|
|
66
|
+
throw err;
|
|
67
|
+
}
|
|
68
|
+
const maxConcurrentRequests = options.cdn.maxConcurrentRequests || 10;
|
|
69
|
+
const componentLimit = (0, pLimit_1.default)(maxConcurrentRequests);
|
|
70
|
+
const packageLimit = (0, pLimit_1.default)(maxConcurrentRequests);
|
|
71
|
+
const rowsByComponent = await Promise.all(components.map((name) => componentLimit(async () => {
|
|
72
|
+
const versions = await options.cdn.listSubDirectories(`${options.componentsDir}/${name}`);
|
|
73
|
+
const rows = await Promise.all(versions.map((version) => packageLimit(async () => {
|
|
74
|
+
try {
|
|
75
|
+
const component = await options.cdn.getJson(`${options.componentsDir}/${name}/${version}/package.json`, true);
|
|
76
|
+
const row = {
|
|
77
|
+
name,
|
|
78
|
+
version,
|
|
79
|
+
publishDate: component.oc.date || 0
|
|
80
|
+
};
|
|
81
|
+
const templateSize = getTemplateSize(component);
|
|
82
|
+
if (templateSize !== undefined) {
|
|
83
|
+
row.templateSize = templateSize;
|
|
84
|
+
}
|
|
85
|
+
return row;
|
|
86
|
+
}
|
|
87
|
+
catch (err) {
|
|
88
|
+
if (isNotFoundError(err, 'file_not_found')) {
|
|
89
|
+
return null;
|
|
90
|
+
}
|
|
91
|
+
throw err;
|
|
92
|
+
}
|
|
93
|
+
})));
|
|
94
|
+
return rows.filter((row) => row !== null);
|
|
95
|
+
})));
|
|
96
|
+
return rowsByComponent.flat();
|
|
97
|
+
};
|
|
98
|
+
exports.getComponentRowsFromStorageDirectories = getComponentRowsFromStorageDirectories;
|
|
99
|
+
const backfillMetadataFromStorageDirectories = async (options) => (0, exports.backfillMetadataRows)(options.metadataStore, await (0, exports.getComponentRowsFromStorageDirectories)(options));
|
|
100
|
+
exports.backfillMetadataFromStorageDirectories = backfillMetadataFromStorageDirectories;
|
|
101
|
+
exports.reconcileMetadataFromStorage = exports.backfillMetadataFromStorageDirectories;
|
|
102
|
+
const exportLegacyMetadataFiles = async (options) => {
|
|
103
|
+
const rows = await options.metadataStore.getAllComponents();
|
|
104
|
+
const componentsListPath = `${options.componentsDir}/components.json`;
|
|
105
|
+
const componentsDetailsPath = `${options.componentsDir}/components-details.json`;
|
|
106
|
+
await Promise.all([
|
|
107
|
+
options.cdn.putFileContent(JSON.stringify((0, metadata_index_1.getComponentsListFromRows)(rows)), componentsListPath, true),
|
|
108
|
+
options.cdn.putFileContent(JSON.stringify((0, metadata_index_1.getComponentsDetailsFromRows)(rows)), componentsDetailsPath, true)
|
|
109
|
+
]);
|
|
110
|
+
return {
|
|
111
|
+
exported: rows.length,
|
|
112
|
+
files: [componentsListPath, componentsDetailsPath]
|
|
113
|
+
};
|
|
114
|
+
};
|
|
115
|
+
exports.exportLegacyMetadataFiles = exportLegacyMetadataFiles;
|
|
116
|
+
const backfillMetadataFromStorageDetails = async (options) => {
|
|
117
|
+
try {
|
|
118
|
+
const componentsDetails = await options.cdn.getJson(`${options.componentsDir}/components-details.json`, true);
|
|
119
|
+
return (0, exports.backfillMetadataFromComponentsDetails)(options.metadataStore, componentsDetails);
|
|
120
|
+
}
|
|
121
|
+
catch (err) {
|
|
122
|
+
if (isNotFoundError(err, 'file_not_found')) {
|
|
123
|
+
return (0, exports.backfillMetadataFromStorageDirectories)(options);
|
|
124
|
+
}
|
|
125
|
+
throw err;
|
|
126
|
+
}
|
|
127
|
+
};
|
|
128
|
+
exports.backfillMetadataFromStorageDetails = backfillMetadataFromStorageDetails;
|
|
@@ -41,11 +41,16 @@ const node_path_1 = __importDefault(require("node:path"));
|
|
|
41
41
|
const dotenv_1 = __importDefault(require("dotenv"));
|
|
42
42
|
const fs_extra_1 = __importDefault(require("fs-extra"));
|
|
43
43
|
const oc_get_unix_utc_timestamp_1 = __importDefault(require("oc-get-unix-utc-timestamp"));
|
|
44
|
+
const oc_metadata_adapters_utils_1 = require("oc-metadata-adapters-utils");
|
|
44
45
|
const resources_1 = __importDefault(require("../../resources"));
|
|
45
46
|
const settings_1 = __importDefault(require("../../resources/settings"));
|
|
46
47
|
const error_to_string_1 = __importDefault(require("../../utils/error-to-string"));
|
|
47
48
|
const components_cache_1 = __importDefault(require("./components-cache"));
|
|
48
49
|
const components_details_1 = __importDefault(require("./components-details"));
|
|
50
|
+
const events_handler_1 = __importDefault(require("./events-handler"));
|
|
51
|
+
const metadata_adapter_options_1 = __importDefault(require("./metadata-adapter-options"));
|
|
52
|
+
const metadata_index_1 = require("./metadata-index");
|
|
53
|
+
const metadata_migration_1 = require("./metadata-migration");
|
|
49
54
|
const register_templates_1 = __importDefault(require("./register-templates"));
|
|
50
55
|
const storage_adapter_1 = __importDefault(require("./storage-adapter"));
|
|
51
56
|
const validator = __importStar(require("./validators"));
|
|
@@ -58,10 +63,56 @@ function repository(conf) {
|
|
|
58
63
|
const repositorySource = conf.local
|
|
59
64
|
? 'local repository'
|
|
60
65
|
: cdn.adapterType + ' cdn';
|
|
61
|
-
const
|
|
62
|
-
|
|
66
|
+
const metadataStore = !conf.local && conf.metadata
|
|
67
|
+
? conf.metadata.adapter((0, metadata_adapter_options_1.default)(conf))
|
|
68
|
+
: undefined;
|
|
69
|
+
const metadataIndex = metadataStore
|
|
70
|
+
? (0, metadata_index_1.createMetadataIndex)(metadataStore)
|
|
71
|
+
: undefined;
|
|
72
|
+
const componentsCache = (0, components_cache_1.default)(conf, cdn, metadataIndex);
|
|
73
|
+
const componentsDetails = (0, components_details_1.default)(conf, cdn, metadataIndex);
|
|
74
|
+
let exportLegacyFilesLoop;
|
|
75
|
+
let closed = false;
|
|
63
76
|
const getFilePath = (component, version, filePath) => `${options.componentsDir}/${component}/${version}/${filePath}`;
|
|
77
|
+
const exportLegacyFiles = () => {
|
|
78
|
+
if (!metadataStore || !conf.metadata?.exportLegacyFiles) {
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
return (0, metadata_migration_1.exportLegacyMetadataFiles)({
|
|
82
|
+
metadataStore,
|
|
83
|
+
cdn,
|
|
84
|
+
componentsDir: options.componentsDir
|
|
85
|
+
}).catch((err) => events_handler_1.default.fire('error', {
|
|
86
|
+
code: 'metadata_legacy_files_export',
|
|
87
|
+
message: err?.message || String(err)
|
|
88
|
+
}));
|
|
89
|
+
};
|
|
90
|
+
// Run the DB→components.json export on a non-overlapping background timer
|
|
91
|
+
// instead of on the publish path, so a publish stays an O(1) append rather
|
|
92
|
+
// than triggering a full-registry scan + blob rewrite. The timer only runs
|
|
93
|
+
// when an interval is explicitly configured.
|
|
94
|
+
const scheduleLegacyFilesExport = () => {
|
|
95
|
+
const intervalSeconds = conf.metadata?.exportLegacyFilesInterval;
|
|
96
|
+
if (closed ||
|
|
97
|
+
!metadataStore ||
|
|
98
|
+
!conf.metadata?.exportLegacyFiles ||
|
|
99
|
+
!intervalSeconds) {
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
exportLegacyFilesLoop = setTimeout(async () => {
|
|
103
|
+
await exportLegacyFiles();
|
|
104
|
+
if (!closed) {
|
|
105
|
+
scheduleLegacyFilesExport();
|
|
106
|
+
}
|
|
107
|
+
}, intervalSeconds * 1000);
|
|
108
|
+
};
|
|
64
109
|
const { templatesHash, templatesInfo } = (0, register_templates_1.default)(conf.templates, conf.local);
|
|
110
|
+
const throwVersionAlreadyFound = (componentName, componentVersion) => {
|
|
111
|
+
throw {
|
|
112
|
+
code: resources_1.default.errors.registry.COMPONENT_VERSION_ALREADY_FOUND_CODE,
|
|
113
|
+
msg: resources_1.default.errors.registry.COMPONENT_VERSION_ALREADY_FOUND(componentName, componentVersion, repositorySource)
|
|
114
|
+
};
|
|
115
|
+
};
|
|
65
116
|
const local = {
|
|
66
117
|
components: undefined,
|
|
67
118
|
getCompiledView(componentName) {
|
|
@@ -222,8 +273,21 @@ function repository(conf) {
|
|
|
222
273
|
// when in local this won't get called
|
|
223
274
|
return;
|
|
224
275
|
}
|
|
276
|
+
if (metadataStore) {
|
|
277
|
+
await metadataStore.initialise();
|
|
278
|
+
if (conf.metadata?.reconcileFromStorage) {
|
|
279
|
+
await (0, metadata_migration_1.reconcileMetadataFromStorage)({
|
|
280
|
+
metadataStore,
|
|
281
|
+
cdn,
|
|
282
|
+
componentsDir: options.componentsDir
|
|
283
|
+
});
|
|
284
|
+
}
|
|
285
|
+
}
|
|
225
286
|
const componentsList = await componentsCache.load();
|
|
226
|
-
|
|
287
|
+
const details = await componentsDetails.refresh(componentsList);
|
|
288
|
+
void exportLegacyFiles();
|
|
289
|
+
scheduleLegacyFilesExport();
|
|
290
|
+
return details;
|
|
227
291
|
},
|
|
228
292
|
async publishComponent({ componentName, componentVersion, pkgDetails, user, dryRun = false }) {
|
|
229
293
|
if (conf.local) {
|
|
@@ -257,21 +321,56 @@ function repository(conf) {
|
|
|
257
321
|
}
|
|
258
322
|
const componentVersions = await repository.getComponentVersions(componentName);
|
|
259
323
|
if (!versionHandler.validateNewVersion(componentVersion, componentVersions)) {
|
|
260
|
-
|
|
261
|
-
code: resources_1.default.errors.registry.COMPONENT_VERSION_ALREADY_FOUND_CODE,
|
|
262
|
-
msg: resources_1.default.errors.registry.COMPONENT_VERSION_ALREADY_FOUND(componentName, componentVersion, repositorySource)
|
|
263
|
-
};
|
|
324
|
+
throwVersionAlreadyFound(componentName, componentVersion);
|
|
264
325
|
}
|
|
265
326
|
pkgDetails.packageJson.oc.date = (0, oc_get_unix_utc_timestamp_1.default)();
|
|
266
327
|
pkgDetails.packageJson.oc.publisher = user;
|
|
267
328
|
if (dryRun)
|
|
268
329
|
return;
|
|
269
330
|
await fs_extra_1.default.writeJson(node_path_1.default.join(pkgDetails.outputFolder, 'package.json'), pkgDetails.packageJson);
|
|
331
|
+
if (metadataStore) {
|
|
332
|
+
const componentRow = (0, metadata_index_1.getComponentRow)(componentName, componentVersion, pkgDetails.packageJson);
|
|
333
|
+
const { token } = await metadataStore
|
|
334
|
+
.reserveVersion(componentRow)
|
|
335
|
+
.catch((err) => {
|
|
336
|
+
if (err?.code === oc_metadata_adapters_utils_1.VERSION_ALREADY_EXISTS ||
|
|
337
|
+
err?.code === oc_metadata_adapters_utils_1.VERSION_PUBLISH_IN_PROGRESS ||
|
|
338
|
+
err?.code ===
|
|
339
|
+
resources_1.default.errors.registry.COMPONENT_VERSION_ALREADY_FOUND_CODE) {
|
|
340
|
+
throwVersionAlreadyFound(componentName, componentVersion);
|
|
341
|
+
}
|
|
342
|
+
throw err;
|
|
343
|
+
});
|
|
344
|
+
try {
|
|
345
|
+
await cdn.putDir(pkgDetails.outputFolder, `${options.componentsDir}/${componentName}/${componentVersion}`);
|
|
346
|
+
await metadataStore.commitVersion(componentName, componentVersion, token);
|
|
347
|
+
metadataIndex.add(componentRow);
|
|
348
|
+
}
|
|
349
|
+
catch (err) {
|
|
350
|
+
await metadataStore
|
|
351
|
+
.abortVersion(componentName, componentVersion, token)
|
|
352
|
+
.catch(() => undefined);
|
|
353
|
+
throw err;
|
|
354
|
+
}
|
|
355
|
+
return;
|
|
356
|
+
}
|
|
270
357
|
await cdn.putDir(pkgDetails.outputFolder, `${options.componentsDir}/${componentName}/${componentVersion}`);
|
|
271
358
|
void componentsCache
|
|
272
359
|
.refresh()
|
|
273
360
|
.then((componentsList) => componentsDetails.refresh(componentsList))
|
|
274
361
|
.catch(() => undefined);
|
|
362
|
+
},
|
|
363
|
+
async close() {
|
|
364
|
+
closed = true;
|
|
365
|
+
componentsCache.close?.();
|
|
366
|
+
componentsDetails.close?.();
|
|
367
|
+
if (exportLegacyFilesLoop) {
|
|
368
|
+
clearTimeout(exportLegacyFilesLoop);
|
|
369
|
+
exportLegacyFilesLoop = undefined;
|
|
370
|
+
}
|
|
371
|
+
if (metadataStore?.close) {
|
|
372
|
+
await metadataStore.close();
|
|
373
|
+
}
|
|
275
374
|
}
|
|
276
375
|
};
|
|
277
376
|
return repository;
|
|
@@ -39,6 +39,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
39
39
|
exports.default = registryConfiguration;
|
|
40
40
|
const resources_1 = __importDefault(require("../../../resources"));
|
|
41
41
|
const auth = __importStar(require("../authentication"));
|
|
42
|
+
const metadata_adapter_options_1 = __importDefault(require("../metadata-adapter-options"));
|
|
42
43
|
function registryConfiguration(conf) {
|
|
43
44
|
const returnError = (message) => {
|
|
44
45
|
return {
|
|
@@ -111,6 +112,26 @@ function registryConfiguration(conf) {
|
|
|
111
112
|
}
|
|
112
113
|
}
|
|
113
114
|
}
|
|
115
|
+
if (!conf.local && conf.metadata) {
|
|
116
|
+
if (!conf.metadata.adapter) {
|
|
117
|
+
return returnError(resources_1.default.errors.registry.CONFIGURATION_METADATA_NOT_VALID('metadata'));
|
|
118
|
+
}
|
|
119
|
+
if (typeof conf.metadata.exportLegacyFilesInterval !== 'undefined') {
|
|
120
|
+
if (!Number.isFinite(conf.metadata.exportLegacyFilesInterval) ||
|
|
121
|
+
conf.metadata.exportLegacyFilesInterval <= 0) {
|
|
122
|
+
return returnError(resources_1.default.errors.registry
|
|
123
|
+
.CONFIGURATION_METADATA_EXPORT_INTERVAL_NOT_VALID);
|
|
124
|
+
}
|
|
125
|
+
if (conf.metadata.exportLegacyFiles !== true) {
|
|
126
|
+
return returnError(resources_1.default.errors.registry
|
|
127
|
+
.CONFIGURATION_METADATA_EXPORT_INTERVAL_WITHOUT_EXPORT_NOT_VALID);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
const metadataStore = conf.metadata.adapter((0, metadata_adapter_options_1.default)(conf));
|
|
131
|
+
if (!metadataStore.isValid()) {
|
|
132
|
+
return returnError(resources_1.default.errors.registry.CONFIGURATION_METADATA_NOT_VALID(metadataStore.adapterType));
|
|
133
|
+
}
|
|
134
|
+
}
|
|
114
135
|
if (conf.customHeadersToSkipOnWeakVersion) {
|
|
115
136
|
if (!Array.isArray(conf.customHeadersToSkipOnWeakVersion)) {
|
|
116
137
|
return returnError(resources_1.default.errors.registry
|
package/dist/registry/index.d.ts
CHANGED
|
@@ -4,7 +4,7 @@ import type { Plugin } from '../types';
|
|
|
4
4
|
import { RegistryOptions } from './domain/options-sanitiser';
|
|
5
5
|
export { RegistryOptions };
|
|
6
6
|
export default function registry<T = any>(inputOptions: RegistryOptions<T>): {
|
|
7
|
-
close: (callback: (err?: Error | undefined | string) => void) => void
|
|
7
|
+
close: (callback: (err?: Error | undefined | string) => void) => void;
|
|
8
8
|
on: <T_1 extends keyof {
|
|
9
9
|
error: {
|
|
10
10
|
code: string;
|
package/dist/registry/index.js
CHANGED
|
@@ -59,10 +59,14 @@ function registry(inputOptions) {
|
|
|
59
59
|
let server;
|
|
60
60
|
const repository = (0, repository_1.default)(options);
|
|
61
61
|
const close = (callback) => {
|
|
62
|
+
const closeMetadataStore = () => Promise.resolve(repository.close?.()).catch(() => undefined);
|
|
62
63
|
if (server?.listening) {
|
|
63
|
-
|
|
64
|
+
server.close((err) => {
|
|
65
|
+
void closeMetadataStore().finally(() => callback(err));
|
|
66
|
+
});
|
|
67
|
+
return;
|
|
64
68
|
}
|
|
65
|
-
|
|
69
|
+
void closeMetadataStore().finally(() => callback('not opened'));
|
|
66
70
|
};
|
|
67
71
|
const register = (plugin, callback) => {
|
|
68
72
|
plugins.push(Object.assign(plugin, { callback }));
|
|
@@ -28,6 +28,9 @@ declare const _default: {
|
|
|
28
28
|
COMPONENT_SET_COOKIE_PARAMETERS_NOT_VALID: string;
|
|
29
29
|
CONFIGURATION_DEPENDENCIES_MUST_BE_ARRAY: string;
|
|
30
30
|
CONFIGURATION_EMPTY: string;
|
|
31
|
+
CONFIGURATION_METADATA_NOT_VALID: (adapterType: string) => string;
|
|
32
|
+
CONFIGURATION_METADATA_EXPORT_INTERVAL_NOT_VALID: string;
|
|
33
|
+
CONFIGURATION_METADATA_EXPORT_INTERVAL_WITHOUT_EXPORT_NOT_VALID: string;
|
|
31
34
|
CONFIGURATION_ONREQUEST_MUST_BE_FUNCTION: string;
|
|
32
35
|
CONFIGURATION_OFFREQUEST_MUST_BE_FUNCTION: string;
|
|
33
36
|
CONFIGURATION_PUBLISH_BASIC_AUTH_CREDENTIALS_MISSING: string;
|
package/dist/resources/index.js
CHANGED
|
@@ -77,6 +77,9 @@ exports.default = {
|
|
|
77
77
|
COMPONENT_SET_COOKIE_PARAMETERS_NOT_VALID: 'context.setCookie parameters are not valid',
|
|
78
78
|
CONFIGURATION_DEPENDENCIES_MUST_BE_ARRAY: 'Registry configuration is not valid: dependencies must be an array',
|
|
79
79
|
CONFIGURATION_EMPTY: 'Registry configuration is empty',
|
|
80
|
+
CONFIGURATION_METADATA_NOT_VALID: (adapterType) => `Registry configuration is not valid: ${adapterType} is not a valid metadata adapter`,
|
|
81
|
+
CONFIGURATION_METADATA_EXPORT_INTERVAL_NOT_VALID: 'Registry configuration is not valid: metadata.exportLegacyFilesInterval must be a positive number',
|
|
82
|
+
CONFIGURATION_METADATA_EXPORT_INTERVAL_WITHOUT_EXPORT_NOT_VALID: 'Registry configuration is not valid: metadata.exportLegacyFilesInterval requires metadata.exportLegacyFiles to be true',
|
|
80
83
|
CONFIGURATION_ONREQUEST_MUST_BE_FUNCTION: "Registry configuration is not valid: registry.on's callback must be a function",
|
|
81
84
|
CONFIGURATION_OFFREQUEST_MUST_BE_FUNCTION: "Registry configuration is not valid: registry.off's callback must be a function",
|
|
82
85
|
CONFIGURATION_PUBLISH_BASIC_AUTH_CREDENTIALS_MISSING: 'Registry configuration is not valid: basic auth requires username and password',
|
package/dist/types.d.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import type { NextFunction, Request, Response } from 'express';
|
|
2
|
+
import type { MetadataStore as MetadataStoreType } from 'oc-metadata-adapters-utils';
|
|
2
3
|
import type { StorageAdapter } from 'oc-storage-adapters-utils';
|
|
3
4
|
import type { PackageJson } from 'type-fest';
|
|
5
|
+
export type { ComponentRow, MetadataStore } from 'oc-metadata-adapters-utils';
|
|
4
6
|
type Middleware = (req: Request, res: Response, next: NextFunction) => void;
|
|
5
7
|
export interface Author {
|
|
6
8
|
email?: string;
|
|
@@ -38,6 +40,44 @@ export interface ComponentsList {
|
|
|
38
40
|
components: Record<string, string[]>;
|
|
39
41
|
lastEdit: number;
|
|
40
42
|
}
|
|
43
|
+
export interface MetadataConfig<T = any> {
|
|
44
|
+
/**
|
|
45
|
+
* Factory for the metadata store. It must be side-effect free because config
|
|
46
|
+
* validation may instantiate a throwaway store before registry startup.
|
|
47
|
+
*/
|
|
48
|
+
adapter: (options: T) => MetadataStoreType;
|
|
49
|
+
options: T;
|
|
50
|
+
/**
|
|
51
|
+
* Controls whether OC asks the metadata adapter to manage its schema/table.
|
|
52
|
+
* When omitted, OC behaves as `true` and passes that default to adapter
|
|
53
|
+
* validation/startup/migration paths.
|
|
54
|
+
*
|
|
55
|
+
* @default true
|
|
56
|
+
*/
|
|
57
|
+
manageSchema?: boolean;
|
|
58
|
+
/**
|
|
59
|
+
* When true, startup scans storage and inserts any missing metadata rows
|
|
60
|
+
* before cache hydration. Existing rows are skipped.
|
|
61
|
+
*
|
|
62
|
+
* @default false
|
|
63
|
+
*/
|
|
64
|
+
reconcileFromStorage?: boolean;
|
|
65
|
+
/**
|
|
66
|
+
* When true, startup exports DB-derived `components.json` and
|
|
67
|
+
* `components-details.json` projections to storage. This is a one-way export
|
|
68
|
+
* from metadata to storage and is not triggered on each publish.
|
|
69
|
+
*
|
|
70
|
+
* @default false
|
|
71
|
+
*/
|
|
72
|
+
exportLegacyFiles?: boolean;
|
|
73
|
+
/**
|
|
74
|
+
* Interval, in seconds, at which the DB→`components.json` legacy export runs
|
|
75
|
+
* in the background. Only used when `exportLegacyFiles` is true. When omitted,
|
|
76
|
+
* the export runs once at startup only (no background refresh). The export is
|
|
77
|
+
* never coupled to the publish path.
|
|
78
|
+
*/
|
|
79
|
+
exportLegacyFilesInterval?: number;
|
|
80
|
+
}
|
|
41
81
|
export interface OcParameter {
|
|
42
82
|
default?: string | boolean | number;
|
|
43
83
|
description?: string;
|
|
@@ -334,6 +374,7 @@ export interface Config<T = any> {
|
|
|
334
374
|
* @default "/"
|
|
335
375
|
*/
|
|
336
376
|
prefix: string;
|
|
377
|
+
metadata?: MetadataConfig;
|
|
337
378
|
/**
|
|
338
379
|
* Authentication strategy for component publishing.
|
|
339
380
|
*/
|
|
@@ -491,4 +532,3 @@ declare global {
|
|
|
491
532
|
}
|
|
492
533
|
}
|
|
493
534
|
}
|
|
494
|
-
export {};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "oc",
|
|
3
|
-
"version": "0.50.
|
|
3
|
+
"version": "0.50.56",
|
|
4
4
|
"description": "A framework for developing and distributing html components",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"types": "./dist/index.d.ts",
|
|
@@ -98,7 +98,8 @@
|
|
|
98
98
|
"oc-client-browser": "*",
|
|
99
99
|
"oc-empty-response-handler": "^1.0.2",
|
|
100
100
|
"oc-get-unix-utc-timestamp": "^1.0.6",
|
|
101
|
-
"oc-
|
|
101
|
+
"oc-metadata-adapters-utils": "^0.1.1",
|
|
102
|
+
"oc-s3-storage-adapter": "^2.2.3",
|
|
102
103
|
"oc-storage-adapters-utils": "^2.1.2",
|
|
103
104
|
"oc-template-es6": "^2.0.0",
|
|
104
105
|
"oc-template-es6-compiler": "^7.1.4",
|