directus-template-cli 0.4.3 → 0.5.0-beta.10

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.
Files changed (65) hide show
  1. package/dist/commands/apply.d.ts +23 -1
  2. package/dist/commands/apply.js +281 -108
  3. package/dist/commands/extract.d.ts +12 -0
  4. package/dist/commands/extract.js +85 -17
  5. package/dist/lib/constants.d.ts +3 -0
  6. package/dist/lib/constants.js +6 -0
  7. package/dist/lib/extract/extract-access.d.ts +1 -0
  8. package/dist/lib/extract/extract-access.js +25 -0
  9. package/dist/lib/extract/extract-assets.d.ts +257 -25
  10. package/dist/lib/extract/extract-extensions.d.ts +4 -0
  11. package/dist/lib/extract/extract-extensions.js +22 -0
  12. package/dist/lib/extract/extract-fields.js +2 -3
  13. package/dist/lib/extract/extract-permissions.d.ts +3 -0
  14. package/dist/lib/extract/extract-permissions.js +11 -4
  15. package/dist/lib/extract/extract-policies.d.ts +4 -0
  16. package/dist/lib/extract/extract-policies.js +28 -0
  17. package/dist/lib/extract/extract-presets.js +1 -1
  18. package/dist/lib/extract/index.js +6 -0
  19. package/dist/lib/load/index.d.ts +13 -1
  20. package/dist/lib/load/index.js +38 -20
  21. package/dist/lib/load/load-access.d.ts +1 -0
  22. package/dist/lib/load/load-access.js +60 -0
  23. package/dist/lib/load/load-collections.js +47 -21
  24. package/dist/lib/load/load-dashboards.js +32 -11
  25. package/dist/lib/load/load-data.js +72 -46
  26. package/dist/lib/load/load-extensions.d.ts +1 -0
  27. package/dist/lib/load/load-extensions.js +76 -0
  28. package/dist/lib/load/load-files.d.ts +1 -2
  29. package/dist/lib/load/load-files.js +52 -24
  30. package/dist/lib/load/load-flows.d.ts +1 -1
  31. package/dist/lib/load/load-flows.js +48 -32
  32. package/dist/lib/load/load-folders.js +32 -11
  33. package/dist/lib/load/load-permissions.js +15 -12
  34. package/dist/lib/load/load-policies.d.ts +1 -0
  35. package/dist/lib/load/load-policies.js +37 -0
  36. package/dist/lib/load/load-presets.js +26 -10
  37. package/dist/lib/load/load-relations.js +17 -7
  38. package/dist/lib/load/load-roles.js +47 -16
  39. package/dist/lib/load/load-settings.js +44 -4
  40. package/dist/lib/load/load-translations.js +24 -7
  41. package/dist/lib/load/load-users.js +28 -5
  42. package/dist/lib/sdk.d.ts +1 -1
  43. package/dist/lib/sdk.js +10 -3
  44. package/dist/lib/types/extension.d.ts +42 -0
  45. package/dist/lib/types/extension.js +2 -0
  46. package/dist/lib/utils/auth.js +9 -3
  47. package/dist/lib/utils/catch-error.d.ts +6 -0
  48. package/dist/lib/utils/catch-error.js +35 -0
  49. package/dist/lib/utils/check-template.js +1 -16
  50. package/dist/lib/utils/chunk-array.d.ts +1 -0
  51. package/dist/lib/utils/chunk-array.js +7 -0
  52. package/dist/lib/utils/get-role-ids.d.ts +3 -53
  53. package/dist/lib/utils/get-role-ids.js +4 -2
  54. package/dist/lib/utils/get-template.d.ts +9 -0
  55. package/dist/lib/utils/get-template.js +99 -0
  56. package/dist/lib/utils/logger.d.ts +12 -0
  57. package/dist/lib/utils/logger.js +55 -0
  58. package/dist/lib/utils/read-file.js +2 -1
  59. package/dist/lib/utils/read-templates.js +4 -2
  60. package/oclif.manifest.json +192 -5
  61. package/package.json +4 -5
  62. package/dist/lib/load/load-schema.d.ts +0 -14
  63. package/dist/lib/load/load-schema.js +0 -95
  64. package/dist/lib/utils/log-error.d.ts +0 -14
  65. package/dist/lib/utils/log-error.js +0 -25
@@ -0,0 +1,60 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const tslib_1 = require("tslib");
4
+ const core_1 = require("@oclif/core");
5
+ const constants_1 = require("../constants");
6
+ const sdk_1 = require("../sdk");
7
+ const catch_error_1 = tslib_1.__importDefault(require("../utils/catch-error"));
8
+ const read_file_1 = tslib_1.__importDefault(require("../utils/read-file"));
9
+ async function loadAccess(dir) {
10
+ const access = (0, read_file_1.default)('access', dir);
11
+ core_1.ux.action.start(core_1.ux.colorize(constants_1.DIRECTUS_PINK, `Loading ${access.length} accesses`));
12
+ // Fetch existing accesses
13
+ const existingAccesses = await sdk_1.api.client.request(() => ({
14
+ method: 'GET',
15
+ params: {
16
+ limit: -1,
17
+ },
18
+ path: '/access',
19
+ }));
20
+ const existingAccessById = new Map(existingAccesses.map(acc => [acc.id, acc]));
21
+ const existingAccessByCompositeKey = new Map(existingAccesses.map(acc => [getCompositeKey(acc), acc]));
22
+ for await (const acc of access) {
23
+ try {
24
+ if (existingAccessById.has(acc.id)) {
25
+ continue;
26
+ }
27
+ const compositeKey = getCompositeKey(acc);
28
+ if (existingAccessByCompositeKey.has(compositeKey)) {
29
+ continue;
30
+ }
31
+ // If the role is null, delete the role key to avoid errors
32
+ if (acc.role === null) {
33
+ delete acc.role;
34
+ }
35
+ await sdk_1.api.client.request(() => ({
36
+ body: JSON.stringify(acc),
37
+ method: 'POST',
38
+ path: '/access',
39
+ }));
40
+ // Add the new access to our maps
41
+ existingAccessById.set(acc.id, acc);
42
+ existingAccessByCompositeKey.set(compositeKey, acc);
43
+ }
44
+ catch (error) {
45
+ (0, catch_error_1.default)(error, {
46
+ context: {
47
+ access: acc,
48
+ operation: 'createAccess',
49
+ },
50
+ });
51
+ }
52
+ }
53
+ core_1.ux.action.stop();
54
+ }
55
+ exports.default = loadAccess;
56
+ // Helper function to generate a composite key for each access
57
+ function getCompositeKey(acc) {
58
+ var _a, _b;
59
+ return `${(_a = acc.role) !== null && _a !== void 0 ? _a : 'null'}-${(_b = acc.user) !== null && _b !== void 0 ? _b : 'null'}-${acc.policy}`;
60
+ }
@@ -3,36 +3,58 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  const tslib_1 = require("tslib");
4
4
  const sdk_1 = require("@directus/sdk");
5
5
  const core_1 = require("@oclif/core");
6
+ const constants_1 = require("../constants");
6
7
  const sdk_2 = require("../sdk");
7
- const log_error_1 = tslib_1.__importDefault(require("../utils/log-error"));
8
+ const catch_error_1 = tslib_1.__importDefault(require("../utils/catch-error"));
8
9
  const read_file_1 = tslib_1.__importDefault(require("../utils/read-file"));
9
10
  /**
10
11
  * Load collections into the Directus instance
11
12
  */
12
13
  async function loadCollections(dir) {
13
- const collections = (0, read_file_1.default)('collections', dir);
14
- const fields = (0, read_file_1.default)('fields', dir);
15
- core_1.ux.action.start(`Loading ${collections.length} collections and ${fields.length} fields.`);
16
- // Remove the group so that we can create the collections
17
- const removedGroupKey = structuredClone(collections).map(col => {
18
- delete col.meta.group;
19
- return col;
20
- });
21
- await addCollections(removedGroupKey, fields);
22
- await updateCollections(collections);
23
- await addCustomFieldsOnSystemCollections(fields);
14
+ const collectionsToAdd = (0, read_file_1.default)('collections', dir);
15
+ const fieldsToAdd = (0, read_file_1.default)('fields', dir);
16
+ core_1.ux.action.start(core_1.ux.colorize(constants_1.DIRECTUS_PINK, `Loading ${collectionsToAdd.length} collections and ${fieldsToAdd.length} fields`));
17
+ await processCollections(collectionsToAdd, fieldsToAdd);
18
+ await updateCollections(collectionsToAdd);
19
+ await addCustomFieldsOnSystemCollections(fieldsToAdd);
24
20
  core_1.ux.action.stop();
25
- core_1.ux.log('Loaded collections and fields.');
26
21
  }
27
22
  exports.default = loadCollections;
28
- async function addCollections(collections, fields) {
29
- for await (const collection of collections) {
23
+ async function processCollections(collectionsToAdd, fieldsToAdd) {
24
+ const existingCollections = await sdk_2.api.client.request((0, sdk_1.readCollections)());
25
+ const existingFields = await sdk_2.api.client.request((0, sdk_1.readFields)());
26
+ for await (const collection of collectionsToAdd) {
30
27
  try {
31
- collection.fields = fields.filter((field) => field.collection === collection.collection);
32
- await sdk_2.api.client.request((0, sdk_1.createCollection)(collection));
28
+ const existingCollection = existingCollections.find((c) => c.collection === collection.collection);
29
+ await (existingCollection ? addNewFieldsToExistingCollection(collection.collection, fieldsToAdd, existingFields) : addNewCollectionWithFields(collection, fieldsToAdd));
33
30
  }
34
31
  catch (error) {
35
- (0, log_error_1.default)(error);
32
+ (0, catch_error_1.default)(error);
33
+ }
34
+ }
35
+ }
36
+ async function addNewCollectionWithFields(collection, allFields) {
37
+ const collectionFields = allFields.filter(field => field.collection === collection.collection);
38
+ const collectionWithoutGroup = {
39
+ ...collection,
40
+ fields: collectionFields,
41
+ meta: { ...collection.meta },
42
+ };
43
+ delete collectionWithoutGroup.meta.group;
44
+ await sdk_2.api.client.request((0, sdk_1.createCollection)(collectionWithoutGroup));
45
+ }
46
+ async function addNewFieldsToExistingCollection(collectionName, fieldsToAdd, existingFields) {
47
+ const collectionFieldsToAdd = fieldsToAdd.filter(field => field.collection === collectionName);
48
+ const existingCollectionFields = existingFields.filter((field) => field.collection === collectionName);
49
+ for await (const field of collectionFieldsToAdd) {
50
+ if (!existingCollectionFields.some((existingField) => existingField.field === field.field)) {
51
+ try {
52
+ // @ts-ignore
53
+ await sdk_2.api.client.request((0, sdk_1.createField)(collectionName, field));
54
+ }
55
+ catch (error) {
56
+ (0, catch_error_1.default)(error);
57
+ }
36
58
  }
37
59
  }
38
60
  }
@@ -49,18 +71,22 @@ async function updateCollections(collections) {
49
71
  }
50
72
  }
51
73
  catch (error) {
52
- (0, log_error_1.default)(error);
74
+ (0, catch_error_1.default)(error);
53
75
  }
54
76
  }
55
77
  }
56
78
  async function addCustomFieldsOnSystemCollections(fields) {
57
79
  const customFields = fields.filter((field) => field.collection.startsWith('directus_'));
80
+ const existingFields = await sdk_2.api.client.request((0, sdk_1.readFields)());
58
81
  for await (const field of customFields) {
59
82
  try {
60
- await sdk_2.api.client.request((0, sdk_1.createField)(field.collection, field));
83
+ const fieldExists = existingFields.some((existingField) => existingField.collection === field.collection && existingField.field === field.field);
84
+ if (!fieldExists) {
85
+ await sdk_2.api.client.request((0, sdk_1.createField)(field.collection, field));
86
+ }
61
87
  }
62
88
  catch (error) {
63
- (0, log_error_1.default)(error);
89
+ (0, catch_error_1.default)(error);
64
90
  }
65
91
  }
66
92
  }
@@ -4,40 +4,61 @@ exports.loadPanels = void 0;
4
4
  const tslib_1 = require("tslib");
5
5
  const sdk_1 = require("@directus/sdk");
6
6
  const core_1 = require("@oclif/core");
7
+ const constants_1 = require("../constants");
7
8
  const sdk_2 = require("../sdk");
8
- const log_error_1 = tslib_1.__importDefault(require("../utils/log-error"));
9
+ const catch_error_1 = tslib_1.__importDefault(require("../utils/catch-error"));
9
10
  const read_file_1 = tslib_1.__importDefault(require("../utils/read-file"));
10
11
  async function loadDashboards(dir) {
11
12
  const dashboards = (0, read_file_1.default)('dashboards', dir);
12
- core_1.ux.action.start(`Loading ${dashboards.length} dashboards`);
13
- const filteredDashboards = dashboards.map(dash => {
13
+ core_1.ux.action.start(core_1.ux.colorize(constants_1.DIRECTUS_PINK, `Loading ${dashboards.length} dashboards`));
14
+ // Fetch existing dashboards
15
+ const existingDashboards = await sdk_2.api.client.request((0, sdk_1.readDashboards)({
16
+ limit: -1,
17
+ }));
18
+ const existingDashboardIds = new Set(existingDashboards.map(dashboard => dashboard.id));
19
+ const filteredDashboards = dashboards.filter(dashboard => {
20
+ if (existingDashboardIds.has(dashboard.id)) {
21
+ return false;
22
+ }
23
+ return true;
24
+ }).map(dash => {
14
25
  const newDash = { ...dash };
15
26
  delete newDash.panels;
16
27
  return newDash;
17
28
  });
18
- for (const dashboard of filteredDashboards) {
29
+ await Promise.all(filteredDashboards.map(async (dashboard) => {
19
30
  try {
20
31
  await sdk_2.api.client.request((0, sdk_1.createDashboard)(dashboard));
21
32
  }
22
33
  catch (error) {
23
- (0, log_error_1.default)(error);
34
+ (0, catch_error_1.default)(error);
24
35
  }
25
- }
36
+ }));
26
37
  await loadPanels(dir);
27
38
  core_1.ux.action.stop();
28
- core_1.ux.log('Loaded dashboards');
29
39
  }
30
40
  exports.default = loadDashboards;
31
41
  async function loadPanels(dir) {
32
42
  const panels = (0, read_file_1.default)('panels', dir);
33
- core_1.ux.log(`Loading ${panels.length} panels`);
34
- for (const panel of panels) {
43
+ core_1.ux.action.status = `Loading ${panels.length} panels`;
44
+ // Fetch existing panels
45
+ const existingPanels = await sdk_2.api.client.request((0, sdk_1.readPanels)({
46
+ limit: -1,
47
+ }));
48
+ const existingPanelIds = new Set(existingPanels.map(panel => panel.id));
49
+ const filteredPanels = panels.filter(panel => {
50
+ if (existingPanelIds.has(panel.id)) {
51
+ return false;
52
+ }
53
+ return true;
54
+ });
55
+ await Promise.all(filteredPanels.map(async (panel) => {
35
56
  try {
36
57
  await sdk_2.api.client.request((0, sdk_1.createPanel)(panel));
37
58
  }
38
59
  catch (error) {
39
- (0, log_error_1.default)(error);
60
+ (0, catch_error_1.default)(error);
40
61
  }
41
- }
62
+ }));
42
63
  }
43
64
  exports.loadPanels = loadPanels;
@@ -4,93 +4,119 @@ const tslib_1 = require("tslib");
4
4
  const sdk_1 = require("@directus/sdk");
5
5
  const core_1 = require("@oclif/core");
6
6
  const node_path_1 = tslib_1.__importDefault(require("node:path"));
7
+ const constants_1 = require("../constants");
7
8
  const sdk_2 = require("../sdk");
8
- const log_error_1 = tslib_1.__importDefault(require("../utils/log-error"));
9
+ const catch_error_1 = tslib_1.__importDefault(require("../utils/catch-error"));
10
+ const chunk_array_1 = require("../utils/chunk-array");
9
11
  const read_file_1 = tslib_1.__importDefault(require("../utils/read-file"));
12
+ const BATCH_SIZE = 50;
10
13
  async function loadData(dir) {
11
14
  const collections = (0, read_file_1.default)('collections', dir);
12
- core_1.ux.action.start(`Loading data for ${collections.length} collections`);
15
+ core_1.ux.action.start(core_1.ux.colorize(constants_1.DIRECTUS_PINK, `Loading data for ${collections.length} collections`));
13
16
  await loadSkeletonRecords(dir);
14
17
  await loadFullData(dir);
15
18
  await loadSingletons(dir);
16
19
  core_1.ux.action.stop();
17
- core_1.ux.log('Loaded data.');
18
20
  }
19
21
  exports.default = loadData;
20
22
  async function loadSkeletonRecords(dir) {
21
- core_1.ux.log('Loading skeleton records');
23
+ core_1.ux.action.status = 'Loading skeleton records';
22
24
  const collections = (0, read_file_1.default)('collections', dir);
23
25
  const primaryKeyMap = await getCollectionPrimaryKeys(dir);
24
26
  const userCollections = collections
25
27
  .filter(item => !item.collection.startsWith('directus_', 0))
26
- .filter(item => item.schema !== null) // Filter our any "folders"
27
- .filter(item => !item.meta.singleton); // Filter out any singletons
28
- for (const collection of userCollections) {
28
+ .filter(item => item.schema !== null)
29
+ .filter(item => !item.meta.singleton);
30
+ await Promise.all(userCollections.map(async (collection) => {
29
31
  const name = collection.collection;
30
32
  const primaryKeyField = getPrimaryKey(primaryKeyMap, name);
31
33
  const sourceDir = node_path_1.default.resolve(dir, 'content');
32
34
  const data = (0, read_file_1.default)(name, sourceDir);
33
- for (const entry of data) {
34
- try {
35
- await sdk_2.api.client.request((0, sdk_1.createItem)(name, {
36
- [primaryKeyField]: entry[primaryKeyField],
37
- }));
38
- }
39
- catch (error) {
40
- (0, log_error_1.default)(error);
41
- }
35
+ // Fetch existing primary keys
36
+ const existingPrimaryKeys = await getExistingPrimaryKeys(name, primaryKeyField);
37
+ // Filter out existing records
38
+ const newData = data.filter(entry => !existingPrimaryKeys.has(entry[primaryKeyField]));
39
+ if (newData.length === 0) {
40
+ core_1.ux.log(`${core_1.ux.colorize('dim', '--')} Skipping ${name}: No new records to add`);
41
+ return;
42
42
  }
43
+ const batches = (0, chunk_array_1.chunkArray)(newData, BATCH_SIZE).map(batch => batch.map(entry => ({ [primaryKeyField]: entry[primaryKeyField] })));
44
+ await Promise.all(batches.map(batch => uploadBatch(name, batch, sdk_1.createItems)));
45
+ core_1.ux.log(`${core_1.ux.colorize('dim', '--')} Added ${newData.length} new skeleton records to ${name}`);
46
+ }));
47
+ core_1.ux.action.status = 'Loaded skeleton records';
48
+ }
49
+ async function getExistingPrimaryKeys(collection, primaryKeyField) {
50
+ const existingKeys = new Set();
51
+ let page = 1;
52
+ const limit = 1000; // Adjust based on your needs and API limits
53
+ while (true) {
54
+ try {
55
+ // @ts-expect-error string
56
+ const response = await sdk_2.api.client.request((0, sdk_1.readItems)(collection, {
57
+ fields: [primaryKeyField],
58
+ limit,
59
+ page,
60
+ }));
61
+ if (response.length === 0)
62
+ break;
63
+ for (const item of response)
64
+ existingKeys.add(item[primaryKeyField]);
65
+ if (response.length < limit)
66
+ break;
67
+ page++;
68
+ }
69
+ catch (error) {
70
+ (0, catch_error_1.default)(error);
71
+ break;
72
+ }
73
+ }
74
+ return existingKeys;
75
+ }
76
+ async function uploadBatch(collection, batch, method) {
77
+ try {
78
+ await sdk_2.api.client.request(method(collection, batch));
79
+ }
80
+ catch (error) {
81
+ (0, catch_error_1.default)(error);
43
82
  }
44
- core_1.ux.log('Loaded skeleton records');
45
83
  }
46
84
  async function loadFullData(dir) {
47
- core_1.ux.log('Updating records with full data');
85
+ core_1.ux.action.status = 'Updating records with full data';
48
86
  const collections = (0, read_file_1.default)('collections', dir);
49
- const primaryKeyMap = await getCollectionPrimaryKeys(dir);
50
87
  const userCollections = collections
51
88
  .filter(item => !item.collection.startsWith('directus_', 0))
52
- .filter(item => item.schema !== null) // Filter our any "folders"
53
- .filter(item => !item.meta.singleton); // Filter out any singletons
54
- for (const collection of userCollections) {
89
+ .filter(item => item.schema !== null)
90
+ .filter(item => !item.meta.singleton);
91
+ await Promise.all(userCollections.map(async (collection) => {
55
92
  const name = collection.collection;
56
- const primaryKeyField = getPrimaryKey(primaryKeyMap, name);
57
93
  const sourceDir = node_path_1.default.resolve(dir, 'content');
58
94
  const data = (0, read_file_1.default)(name, sourceDir);
59
- try {
60
- for (const row of data) {
61
- delete row.user_created;
62
- delete row.user_updated;
63
- await sdk_2.api.client.request((0, sdk_1.updateItem)(name, row[primaryKeyField], row));
64
- }
65
- }
66
- catch (error) {
67
- (0, log_error_1.default)(error);
68
- }
69
- }
70
- core_1.ux.log('Updated records with full data');
95
+ const batches = (0, chunk_array_1.chunkArray)(data, BATCH_SIZE).map(batch => batch.map(({ user_created, user_updated, ...cleanedRow }) => cleanedRow));
96
+ await Promise.all(batches.map(batch => uploadBatch(name, batch, sdk_1.updateItemsBatch)));
97
+ }));
98
+ core_1.ux.action.status = 'Updated records with full data';
71
99
  }
72
100
  async function loadSingletons(dir) {
73
- core_1.ux.log('Loading data for singleton collections');
101
+ core_1.ux.action.status = 'Loading data for singleton collections';
74
102
  const collections = (0, read_file_1.default)('collections', dir);
75
103
  const singletonCollections = collections
76
104
  .filter(item => !item.collection.startsWith('directus_', 0))
77
105
  .filter(item => item.meta.singleton);
78
- for (const collection of singletonCollections) {
106
+ await Promise.all(singletonCollections.map(async (collection) => {
79
107
  const name = collection.collection;
80
108
  const sourceDir = node_path_1.default.resolve(dir, 'content');
81
109
  const data = (0, read_file_1.default)(name, sourceDir);
82
110
  try {
83
- // @ts-ignore
84
- delete data.user_created;
85
- // @ts-ignore
86
- delete data.user_updated;
87
- await sdk_2.api.client.request((0, sdk_1.updateSingleton)(name, data));
111
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
112
+ const { user_created, user_updated, ...cleanedData } = data;
113
+ await sdk_2.api.client.request((0, sdk_1.updateSingleton)(name, cleanedData));
88
114
  }
89
115
  catch (error) {
90
- (0, log_error_1.default)(error);
116
+ (0, catch_error_1.default)(error);
91
117
  }
92
- }
93
- core_1.ux.log('Loaded data for singleton collections');
118
+ }));
119
+ core_1.ux.action.status = 'Loaded data for singleton collections';
94
120
  }
95
121
  async function getCollectionPrimaryKeys(dir) {
96
122
  var _a;
@@ -105,7 +131,7 @@ async function getCollectionPrimaryKeys(dir) {
105
131
  }
106
132
  function getPrimaryKey(collectionsMap, collection) {
107
133
  if (!collectionsMap[collection]) {
108
- throw new Error(`Collection ${collection} not found in collections map`);
134
+ (0, catch_error_1.default)(`Collection ${collection} not found in collections map`);
109
135
  }
110
136
  return collectionsMap[collection];
111
137
  }
@@ -0,0 +1 @@
1
+ export default function loadExtensions(dir: string): Promise<void>;
@@ -0,0 +1,76 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const tslib_1 = require("tslib");
4
+ const sdk_1 = require("@directus/sdk");
5
+ const core_1 = require("@oclif/core");
6
+ const constants_1 = require("../constants");
7
+ const sdk_2 = require("../sdk");
8
+ const catch_error_1 = tslib_1.__importDefault(require("../utils/catch-error"));
9
+ const read_file_1 = tslib_1.__importDefault(require("../utils/read-file"));
10
+ async function installExtension(extension) {
11
+ await sdk_2.api.client.request((0, sdk_1.customEndpoint)({
12
+ body: JSON.stringify({
13
+ extension: extension.id,
14
+ version: extension.version,
15
+ }),
16
+ method: 'POST',
17
+ path: '/extensions/registry/install',
18
+ }));
19
+ }
20
+ async function loadExtensions(dir) {
21
+ core_1.ux.action.start(core_1.ux.colorize(constants_1.DIRECTUS_PINK, 'Loading extensions'));
22
+ try {
23
+ const extensions = (0, read_file_1.default)('extensions', dir);
24
+ if (extensions && extensions.length > 0) {
25
+ const installedExtensions = await sdk_2.api.client.request((0, sdk_1.readExtensions)());
26
+ const registryExtensions = extensions.filter(ext => { var _a; return ((_a = ext.meta) === null || _a === void 0 ? void 0 : _a.source) === 'registry' && !ext.bundle; });
27
+ const bundles = [...new Set(extensions.filter(ext => ext.bundle).map(ext => ext.bundle))];
28
+ const localExtensions = extensions.filter(ext => { var _a; return ((_a = ext.meta) === null || _a === void 0 ? void 0 : _a.source) === 'local'; });
29
+ const extensionsToInstall = extensions.filter(ext => {
30
+ var _a;
31
+ return ((_a = ext.meta) === null || _a === void 0 ? void 0 : _a.source) === 'registry'
32
+ && !ext.bundle
33
+ // @ts-expect-error
34
+ && !installedExtensions.some(installed => installed.id === ext.id);
35
+ });
36
+ core_1.ux.log(`Found ${extensions.length} extensions total: ${registryExtensions.length} registry extensions (including ${bundles.length} bundles), and ${localExtensions.length} local extensions`);
37
+ if (extensionsToInstall.length > 0) {
38
+ core_1.ux.action.start(core_1.ux.colorize(constants_1.DIRECTUS_PINK, `Installing ${extensionsToInstall.length} extensions`));
39
+ const results = await Promise.allSettled(extensionsToInstall.map(async (ext) => {
40
+ var _a, _b, _c;
41
+ try {
42
+ await installExtension({
43
+ id: ext.id,
44
+ // The extension version UUID is the folder name
45
+ version: (_a = ext.meta) === null || _a === void 0 ? void 0 : _a.folder,
46
+ });
47
+ return `-- Installed ${(_b = ext.schema) === null || _b === void 0 ? void 0 : _b.name}`;
48
+ }
49
+ catch (error) {
50
+ (0, catch_error_1.default)(error);
51
+ return `-- Failed to install ${(_c = ext.schema) === null || _c === void 0 ? void 0 : _c.name}`;
52
+ }
53
+ }));
54
+ for (const result of results) {
55
+ if (result.status === 'fulfilled') {
56
+ core_1.ux.log(result.value);
57
+ }
58
+ }
59
+ core_1.ux.action.stop();
60
+ core_1.ux.log('Finished installing extensions');
61
+ }
62
+ else {
63
+ // All extensions are already installed
64
+ core_1.ux.log('All extensions are already installed');
65
+ }
66
+ if (localExtensions.length > 0) {
67
+ core_1.ux.log(`Note: ${localExtensions.length} local extensions need to be installed manually.`);
68
+ }
69
+ }
70
+ }
71
+ catch {
72
+ core_1.ux.log(`${core_1.ux.colorize('dim', '--')} No extensions found or extensions file is empty. Skipping extension installation.`);
73
+ }
74
+ core_1.ux.action.stop();
75
+ }
76
+ exports.default = loadExtensions;
@@ -1,2 +1 @@
1
- declare const _default: (dir: string) => Promise<void>;
2
- export default _default;
1
+ export default function loadFiles(dir: string): Promise<void>;
@@ -6,32 +6,60 @@ const core_1 = require("@oclif/core");
6
6
  const formdata_node_1 = require("formdata-node");
7
7
  const node_fs_1 = require("node:fs");
8
8
  const node_path_1 = tslib_1.__importDefault(require("node:path"));
9
+ const constants_1 = require("../constants");
9
10
  const sdk_2 = require("../sdk");
10
- const log_error_1 = tslib_1.__importDefault(require("../utils/log-error"));
11
+ const catch_error_1 = tslib_1.__importDefault(require("../utils/catch-error"));
11
12
  const read_file_1 = tslib_1.__importDefault(require("../utils/read-file"));
12
- exports.default = async (dir) => {
13
+ async function loadFiles(dir) {
13
14
  const files = (0, read_file_1.default)('files', dir);
14
- core_1.ux.action.start(`Loading ${files.length} files`);
15
- for (const asset of files) {
16
- const fileName = asset.filename_disk;
17
- const assetPath = node_path_1.default.resolve(dir, 'assets', fileName);
18
- const fileStream = new Blob([(0, node_fs_1.readFileSync)(assetPath)], { type: asset.type });
19
- const form = new formdata_node_1.FormData();
20
- form.append('id', asset.id);
21
- if (asset.title)
22
- form.append('title', asset.title);
23
- if (asset.description)
24
- form.append('description', asset.description);
25
- if (asset.folder)
26
- form.append('folder', asset.folder);
27
- form.append('file', fileStream, fileName);
28
- try {
29
- await sdk_2.api.client.request((0, sdk_1.uploadFiles)(form));
30
- }
31
- catch (error) {
32
- (0, log_error_1.default)(error);
33
- }
15
+ core_1.ux.action.start(core_1.ux.colorize(constants_1.DIRECTUS_PINK, `Loading ${files.length} files`));
16
+ try {
17
+ const fileIds = files.map(file => file.id);
18
+ // Fetch only the files we're interested in
19
+ const existingFiles = await sdk_2.api.client.request((0, sdk_1.readFiles)({
20
+ fields: ['id', 'filename_disk'],
21
+ filter: {
22
+ id: {
23
+ _in: fileIds,
24
+ },
25
+ },
26
+ limit: -1,
27
+ }));
28
+ const existingFileIds = new Set(existingFiles.map(file => file.id));
29
+ const existingFileNames = new Set(existingFiles.map(file => file.filename_disk));
30
+ const filesToUpload = files.filter(file => {
31
+ if (existingFileIds.has(file.id)) {
32
+ return false;
33
+ }
34
+ if (existingFileNames.has(file.filename_disk)) {
35
+ return false;
36
+ }
37
+ return true;
38
+ });
39
+ await Promise.all(filesToUpload.map(async (asset) => {
40
+ const fileName = asset.filename_disk;
41
+ const assetPath = node_path_1.default.resolve(dir, 'assets', fileName);
42
+ const fileStream = new Blob([(0, node_fs_1.readFileSync)(assetPath)], { type: asset.type });
43
+ const form = new formdata_node_1.FormData();
44
+ form.append('id', asset.id);
45
+ if (asset.title)
46
+ form.append('title', asset.title);
47
+ if (asset.description)
48
+ form.append('description', asset.description);
49
+ if (asset.folder)
50
+ form.append('folder', asset.folder);
51
+ form.append('file', fileStream, fileName);
52
+ try {
53
+ await sdk_2.api.client.request((0, sdk_1.uploadFiles)(form));
54
+ }
55
+ catch (error) {
56
+ (0, catch_error_1.default)(error);
57
+ }
58
+ }));
59
+ }
60
+ catch (error) {
61
+ (0, catch_error_1.default)(error);
34
62
  }
35
63
  core_1.ux.action.stop();
36
- core_1.ux.log('Loaded Files');
37
- };
64
+ }
65
+ exports.default = loadFiles;
@@ -1,2 +1,2 @@
1
1
  export default function loadFlows(dir: string): Promise<void>;
2
- export declare function loadOperations(dir: string): Promise<void>;
2
+ export declare function loadOperations(operations: any[]): Promise<void>;