directus-template-cli 0.5.0-beta.9 → 0.5.1

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 (58) hide show
  1. package/README.md +186 -17
  2. package/dist/commands/apply.d.ts +25 -3
  3. package/dist/commands/apply.js +78 -121
  4. package/dist/commands/extract.d.ts +29 -1
  5. package/dist/commands/extract.js +72 -50
  6. package/dist/flags/common.d.ts +7 -0
  7. package/dist/flags/common.js +41 -0
  8. package/dist/lib/extract/extract-access.js +5 -3
  9. package/dist/lib/extract/extract-assets.d.ts +0 -414
  10. package/dist/lib/extract/extract-assets.js +29 -25
  11. package/dist/lib/extract/extract-collections.js +5 -4
  12. package/dist/lib/extract/extract-content.d.ts +0 -2
  13. package/dist/lib/extract/extract-content.js +16 -15
  14. package/dist/lib/extract/extract-dashboards.js +8 -6
  15. package/dist/lib/extract/extract-extensions.js +5 -3
  16. package/dist/lib/extract/extract-fields.js +4 -2
  17. package/dist/lib/extract/extract-files.js +5 -3
  18. package/dist/lib/extract/extract-flows.js +8 -6
  19. package/dist/lib/extract/extract-folders.js +5 -3
  20. package/dist/lib/extract/extract-permissions.js +5 -3
  21. package/dist/lib/extract/extract-policies.js +5 -3
  22. package/dist/lib/extract/extract-presets.js +5 -3
  23. package/dist/lib/extract/extract-relations.js +5 -3
  24. package/dist/lib/extract/extract-roles.js +5 -3
  25. package/dist/lib/extract/extract-schema.js +6 -8
  26. package/dist/lib/extract/extract-settings.js +5 -3
  27. package/dist/lib/extract/extract-translations.js +6 -6
  28. package/dist/lib/extract/extract-users.js +6 -6
  29. package/dist/lib/load/apply-flags.d.ts +22 -0
  30. package/dist/lib/load/apply-flags.js +67 -0
  31. package/dist/lib/load/index.js +9 -5
  32. package/dist/lib/load/load-access.js +45 -37
  33. package/dist/lib/load/load-collections.js +20 -2
  34. package/dist/lib/load/load-dashboards.js +27 -25
  35. package/dist/lib/load/load-data.js +3 -3
  36. package/dist/lib/load/load-files.js +42 -46
  37. package/dist/lib/load/load-flows.js +27 -30
  38. package/dist/lib/load/load-folders.js +32 -30
  39. package/dist/lib/load/load-permissions.js +14 -12
  40. package/dist/lib/load/load-policies.js +21 -19
  41. package/dist/lib/load/load-presets.js +25 -23
  42. package/dist/lib/load/load-relations.js +17 -15
  43. package/dist/lib/load/load-roles.js +43 -41
  44. package/dist/lib/load/load-translations.js +22 -20
  45. package/dist/lib/load/load-users.js +42 -39
  46. package/dist/lib/load/update-required-fields.d.ts +1 -0
  47. package/dist/lib/load/update-required-fields.js +24 -0
  48. package/dist/lib/sdk.d.ts +20 -2
  49. package/dist/lib/sdk.js +124 -9
  50. package/dist/lib/utils/auth.d.ts +26 -0
  51. package/dist/lib/utils/auth.js +48 -4
  52. package/dist/lib/utils/catch-error.d.ts +15 -2
  53. package/dist/lib/utils/catch-error.js +31 -25
  54. package/dist/lib/utils/get-template.js +0 -1
  55. package/oclif.manifest.json +74 -28
  56. package/package.json +2 -2
  57. package/dist/lib/interfaces.d.ts +0 -8
  58. package/dist/lib/interfaces.js +0 -2
@@ -10,26 +10,28 @@ const read_file_1 = tslib_1.__importDefault(require("../utils/read-file"));
10
10
  async function loadPolicies(dir) {
11
11
  const policies = (0, read_file_1.default)('policies', dir);
12
12
  core_1.ux.action.start(core_1.ux.colorize(constants_1.DIRECTUS_PINK, `Loading ${policies.length} policies`));
13
- // Fetch existing policies
14
- const existingPolicies = await sdk_2.api.client.request((0, sdk_1.readPolicies)({
15
- limit: -1,
16
- }));
17
- const existingPolicyIds = new Set(existingPolicies.map(policy => policy.id));
18
- const PUBLIC_POLICY_ID = 'abf8a154-5b1c-4a46-ac9c-7300570f4f17';
19
- const policiesWithoutPublic = policies.filter(policy => policy.id !== PUBLIC_POLICY_ID);
20
- for await (const policy of policiesWithoutPublic) {
21
- try {
22
- if (existingPolicyIds.has(policy.id)) {
23
- core_1.ux.action.status = `Skipping existing policy: ${policy.name}`;
24
- continue;
13
+ if (policies && policies.length > 0) {
14
+ // Fetch existing policies
15
+ const existingPolicies = await sdk_2.api.client.request((0, sdk_1.readPolicies)({
16
+ limit: -1,
17
+ }));
18
+ const existingPolicyIds = new Set(existingPolicies.map(policy => policy.id));
19
+ const PUBLIC_POLICY_ID = 'abf8a154-5b1c-4a46-ac9c-7300570f4f17';
20
+ const policiesWithoutPublic = policies.filter(policy => policy.id !== PUBLIC_POLICY_ID);
21
+ for await (const policy of policiesWithoutPublic) {
22
+ try {
23
+ if (existingPolicyIds.has(policy.id)) {
24
+ core_1.ux.action.status = `Skipping existing policy: ${policy.name}`;
25
+ continue;
26
+ }
27
+ // Create new policy
28
+ await sdk_2.api.client.request((0, sdk_1.createPolicy)(policy));
29
+ // Add the new policy ID to our set of existing policies
30
+ existingPolicyIds.add(policy.id);
31
+ }
32
+ catch (error) {
33
+ (0, catch_error_1.default)(error);
25
34
  }
26
- // Create new policy
27
- await sdk_2.api.client.request((0, sdk_1.createPolicy)(policy));
28
- // Add the new policy ID to our set of existing policies
29
- existingPolicyIds.add(policy.id);
30
- }
31
- catch (error) {
32
- (0, catch_error_1.default)(error);
33
35
  }
34
36
  }
35
37
  core_1.ux.action.stop();
@@ -10,31 +10,33 @@ const read_file_1 = tslib_1.__importDefault(require("../utils/read-file"));
10
10
  async function loadPresets(dir) {
11
11
  const presets = (0, read_file_1.default)('presets', dir);
12
12
  core_1.ux.action.start(core_1.ux.colorize(constants_1.DIRECTUS_PINK, `Loading ${presets.length} presets`));
13
- // Fetch existing presets
14
- const existingPresets = await sdk_2.api.client.request((0, sdk_1.readPresets)({
15
- limit: -1,
16
- }));
17
- const existingPresetIds = new Set(existingPresets.map(preset => preset.id));
18
- const presetsToAdd = presets.filter(preset => {
19
- if (existingPresetIds.has(preset.id)) {
20
- return false;
13
+ if (presets && presets.length > 0) {
14
+ // Fetch existing presets
15
+ const existingPresets = await sdk_2.api.client.request((0, sdk_1.readPresets)({
16
+ limit: -1,
17
+ }));
18
+ const existingPresetIds = new Set(existingPresets.map(preset => preset.id));
19
+ const presetsToAdd = presets.filter(preset => {
20
+ if (existingPresetIds.has(preset.id)) {
21
+ return false;
22
+ }
23
+ return true;
24
+ }).map(preset => {
25
+ const cleanPreset = { ...preset };
26
+ cleanPreset.user = null;
27
+ return cleanPreset;
28
+ });
29
+ if (presetsToAdd.length > 0) {
30
+ try {
31
+ await sdk_2.api.client.request((0, sdk_1.createPresets)(presetsToAdd));
32
+ }
33
+ catch (error) {
34
+ (0, catch_error_1.default)(error);
35
+ }
21
36
  }
22
- return true;
23
- }).map(preset => {
24
- const cleanPreset = { ...preset };
25
- cleanPreset.user = null;
26
- return cleanPreset;
27
- });
28
- if (presetsToAdd.length > 0) {
29
- try {
30
- await sdk_2.api.client.request((0, sdk_1.createPresets)(presetsToAdd));
37
+ else {
38
+ // ux.info('-- No new presets to create')
31
39
  }
32
- catch (error) {
33
- (0, catch_error_1.default)(error);
34
- }
35
- }
36
- else {
37
- // ux.info('-- No new presets to create')
38
40
  }
39
41
  core_1.ux.action.stop();
40
42
  }
@@ -13,21 +13,23 @@ const read_file_1 = tslib_1.__importDefault(require("../utils/read-file"));
13
13
  async function loadRelations(dir) {
14
14
  const relations = (0, read_file_1.default)('relations', dir);
15
15
  core_1.ux.action.start(core_1.ux.colorize(constants_1.DIRECTUS_PINK, `Loading ${relations.length} relations`));
16
- // Fetch existing relations
17
- const existingRelations = await sdk_2.api.client.request((0, sdk_1.readRelations)());
18
- const existingRelationKeys = new Set(existingRelations.map(relation => `${relation.collection}:${relation.field}:${relation.related_collection}`));
19
- const relationsToAdd = relations.filter(relation => {
20
- const key = `${relation.collection}:${relation.field}:${relation.related_collection}`;
21
- if (existingRelationKeys.has(key)) {
22
- return false;
23
- }
24
- return true;
25
- }).map(relation => {
26
- const cleanRelation = { ...relation };
27
- delete cleanRelation.meta.id;
28
- return cleanRelation;
29
- });
30
- await addRelations(relationsToAdd);
16
+ if (relations && relations.length > 0) {
17
+ // Fetch existing relations
18
+ const existingRelations = await sdk_2.api.client.request((0, sdk_1.readRelations)());
19
+ const existingRelationKeys = new Set(existingRelations.map(relation => `${relation.collection}:${relation.field}:${relation.related_collection}`));
20
+ const relationsToAdd = relations.filter(relation => {
21
+ const key = `${relation.collection}:${relation.field}:${relation.related_collection}`;
22
+ if (existingRelationKeys.has(key)) {
23
+ return false;
24
+ }
25
+ return true;
26
+ }).map(relation => {
27
+ const cleanRelation = { ...relation };
28
+ delete cleanRelation.meta.id;
29
+ return cleanRelation;
30
+ });
31
+ await addRelations(relationsToAdd);
32
+ }
31
33
  core_1.ux.action.stop();
32
34
  }
33
35
  exports.default = loadRelations;
@@ -11,50 +11,52 @@ const read_file_1 = tslib_1.__importDefault(require("../utils/read-file"));
11
11
  async function loadRoles(dir) {
12
12
  const roles = (0, read_file_1.default)('roles', dir);
13
13
  core_1.ux.action.start(core_1.ux.colorize(constants_1.DIRECTUS_PINK, `Loading ${roles.length} roles`));
14
- const { legacyAdminRoleId, newAdminRoleId } = await (0, get_role_ids_1.default)(dir);
15
- // Fetch existing roles
16
- const existingRoles = await sdk_2.api.client.request((0, sdk_1.readRoles)({
17
- limit: -1,
18
- }));
19
- const existingRoleIds = new Set(existingRoles.map(role => role.id));
20
- const existingRoleNames = new Set(existingRoles.map(role => role.name.toLowerCase()));
21
- const cleanedUpRoles = roles
22
- .filter(role => role.name !== 'Administrator') // Don't load legacy admin role
23
- .filter(role => !existingRoleNames.has(role.name.toLowerCase())) // Filter out roles with existing names
24
- .map(role => {
25
- const r = { ...role };
26
- delete r.users; // Alias field. User roles will be applied when the users are loaded.
27
- delete r.parent; // We need to load all roles first
28
- return r;
29
- });
30
- for await (const role of cleanedUpRoles) {
31
- try {
32
- if (existingRoleIds.has(role.id)) {
33
- continue;
14
+ if (roles && roles.length > 0) {
15
+ const { legacyAdminRoleId, newAdminRoleId } = await (0, get_role_ids_1.default)(dir);
16
+ // Fetch existing roles
17
+ const existingRoles = await sdk_2.api.client.request((0, sdk_1.readRoles)({
18
+ limit: -1,
19
+ }));
20
+ const existingRoleIds = new Set(existingRoles.map(role => role.id));
21
+ const existingRoleNames = new Set(existingRoles.map(role => role.name.toLowerCase()));
22
+ const cleanedUpRoles = roles
23
+ .filter(role => role.name !== 'Administrator') // Don't load legacy admin role
24
+ .filter(role => !existingRoleNames.has(role.name.toLowerCase())) // Filter out roles with existing names
25
+ .map(role => {
26
+ const r = { ...role };
27
+ delete r.users; // Alias field. User roles will be applied when the users are loaded.
28
+ delete r.parent; // We need to load all roles first
29
+ return r;
30
+ });
31
+ for await (const role of cleanedUpRoles) {
32
+ try {
33
+ if (existingRoleIds.has(role.id)) {
34
+ continue;
35
+ }
36
+ // Create new role
37
+ await sdk_2.api.client.request((0, sdk_1.createRole)(role));
38
+ // Add the new role ID and name to our sets of existing roles
39
+ existingRoleIds.add(role.id);
40
+ existingRoleNames.add(role.name.toLowerCase());
34
41
  }
35
- // Create new role
36
- await sdk_2.api.client.request((0, sdk_1.createRole)(role));
37
- // Add the new role ID and name to our sets of existing roles
38
- existingRoleIds.add(role.id);
39
- existingRoleNames.add(role.name.toLowerCase());
40
- }
41
- catch (error) {
42
- (0, catch_error_1.default)(error);
43
- }
44
- }
45
- // Now add in any parent fields
46
- const rolesWithParents = roles.filter(role => role.parent !== null);
47
- for await (const role of rolesWithParents) {
48
- try {
49
- // Remap any roles where the parent ID is the default admin role
50
- if (role.parent === legacyAdminRoleId) {
51
- role.parent = newAdminRoleId;
42
+ catch (error) {
43
+ (0, catch_error_1.default)(error);
52
44
  }
53
- const simplifiedRole = { parent: role.parent };
54
- await sdk_2.api.client.request((0, sdk_1.updateRole)(role.id, simplifiedRole));
55
45
  }
56
- catch (error) {
57
- (0, catch_error_1.default)(error);
46
+ // Now add in any parent fields
47
+ const rolesWithParents = roles.filter(role => role.parent !== null);
48
+ for await (const role of rolesWithParents) {
49
+ try {
50
+ // Remap any roles where the parent ID is the default admin role
51
+ if (role.parent === legacyAdminRoleId) {
52
+ role.parent = newAdminRoleId;
53
+ }
54
+ const simplifiedRole = { parent: role.parent };
55
+ await sdk_2.api.client.request((0, sdk_1.updateRole)(role.id, simplifiedRole));
56
+ }
57
+ catch (error) {
58
+ (0, catch_error_1.default)(error);
59
+ }
58
60
  }
59
61
  }
60
62
  core_1.ux.action.stop();
@@ -10,28 +10,30 @@ const read_file_1 = tslib_1.__importDefault(require("../utils/read-file"));
10
10
  async function loadTranslations(dir) {
11
11
  const translations = (0, read_file_1.default)('translations', dir);
12
12
  core_1.ux.action.start(core_1.ux.colorize(constants_1.DIRECTUS_PINK, `Loading ${translations.length} translations`));
13
- // Fetch existing translations
14
- const existingTranslations = await sdk_2.api.client.request((0, sdk_1.readTranslations)({
15
- limit: -1,
16
- }));
17
- const existingTranslationKeys = new Set(existingTranslations.map(t => `${t.language}_${t.key}`));
18
- const newTranslations = translations.filter(t => {
19
- const key = `${t.language}_${t.key}`;
20
- if (existingTranslationKeys.has(key)) {
21
- return false;
13
+ if (translations && translations.length > 0) {
14
+ // Fetch existing translations
15
+ const existingTranslations = await sdk_2.api.client.request((0, sdk_1.readTranslations)({
16
+ limit: -1,
17
+ }));
18
+ const existingTranslationKeys = new Set(existingTranslations.map(t => `${t.language}_${t.key}`));
19
+ const newTranslations = translations.filter(t => {
20
+ const key = `${t.language}_${t.key}`;
21
+ if (existingTranslationKeys.has(key)) {
22
+ return false;
23
+ }
24
+ return true;
25
+ });
26
+ if (newTranslations.length > 0) {
27
+ try {
28
+ await sdk_2.api.client.request((0, sdk_1.createTranslations)(newTranslations));
29
+ }
30
+ catch (error) {
31
+ (0, catch_error_1.default)(error);
32
+ }
22
33
  }
23
- return true;
24
- });
25
- if (newTranslations.length > 0) {
26
- try {
27
- await sdk_2.api.client.request((0, sdk_1.createTranslations)(newTranslations));
34
+ else {
35
+ // ux.info('-- No new translations to create')
28
36
  }
29
- catch (error) {
30
- (0, catch_error_1.default)(error);
31
- }
32
- }
33
- else {
34
- // ux.info('-- No new translations to create')
35
37
  }
36
38
  core_1.ux.action.stop();
37
39
  }
@@ -11,45 +11,48 @@ const read_file_1 = tslib_1.__importDefault(require("../utils/read-file"));
11
11
  async function loadUsers(dir) {
12
12
  const users = (0, read_file_1.default)('users', dir);
13
13
  core_1.ux.action.start(core_1.ux.colorize(constants_1.DIRECTUS_PINK, `Loading ${users.length} users`));
14
- const { legacyAdminRoleId, newAdminRoleId } = await (0, get_role_ids_1.default)(dir);
15
- const incomingUserEmails = users.map(user => user.email);
16
- const incomingUserIds = users.map(user => user.id).filter(Boolean);
17
- const existingUsers = await sdk_2.api.client.request((0, sdk_1.readUsers)({
18
- filter: {
19
- _or: [
20
- { email: { _in: incomingUserEmails } },
21
- { id: { _in: incomingUserIds } },
22
- ],
23
- },
24
- limit: -1,
25
- }));
26
- const filteredUsers = users.map(user => {
27
- // If the user is an admin, we need to change their role to the new admin role
28
- const isAdmin = user.role === legacyAdminRoleId;
29
- user.role = isAdmin ? newAdminRoleId : user.role;
30
- // Delete the unneeded fields
31
- delete user.last_page;
32
- delete user.token;
33
- delete user.policies;
34
- // Delete passwords to prevent setting to *******
35
- delete user.password;
36
- return user;
37
- });
38
- for await (const user of filteredUsers) {
39
- const existingUser = existingUsers.find(existing => existing.email === user.email || existing.id === user.id);
40
- if (existingUser) {
41
- // If user already exists, we'll skip creating a new one
42
- delete user.email;
43
- delete user.id;
44
- // You might want to update the existing user here instead
45
- // await api.client.request(updateUser(existingUser.id, user))
46
- continue;
47
- }
48
- try {
49
- await sdk_2.api.client.request((0, sdk_1.createUser)(user));
50
- }
51
- catch (error) {
52
- (0, catch_error_1.default)(error);
14
+ if (users && users.length > 0) {
15
+ const { legacyAdminRoleId, newAdminRoleId } = await (0, get_role_ids_1.default)(dir);
16
+ const existingUsers = await sdk_2.api.client.request((0, sdk_1.readUsers)({
17
+ limit: -1,
18
+ }));
19
+ const filteredUsers = users.map(user => {
20
+ // If the user is an admin, we need to change their role to the new admin role
21
+ const isAdmin = user.role === legacyAdminRoleId;
22
+ user.role = isAdmin ? newAdminRoleId : user.role;
23
+ // Delete the unneeded fields
24
+ delete user.last_page;
25
+ delete user.token;
26
+ delete user.policies;
27
+ // Delete passwords to prevent setting to *******
28
+ delete user.password;
29
+ return user;
30
+ });
31
+ for await (const user of filteredUsers) {
32
+ const existingUserWithSameId = existingUsers && Array.isArray(existingUsers)
33
+ ? existingUsers.find(existing => existing.id === user.id)
34
+ : undefined;
35
+ const existingUserWithSameEmail = existingUsers && Array.isArray(existingUsers)
36
+ ? existingUsers.find(existing => existing.email === user.email)
37
+ : undefined;
38
+ if (existingUserWithSameId) {
39
+ // Skip if there's an existing user with the same id
40
+ continue;
41
+ }
42
+ if (existingUserWithSameEmail) {
43
+ // Delete email if there's an existing user with the same email but different id
44
+ delete user.email;
45
+ }
46
+ if (user.email === null) {
47
+ // Delete email if it's null
48
+ delete user.email;
49
+ }
50
+ try {
51
+ await sdk_2.api.client.request((0, sdk_1.createUser)(user));
52
+ }
53
+ catch (error) {
54
+ (0, catch_error_1.default)(error);
55
+ }
53
56
  }
54
57
  }
55
58
  core_1.ux.action.stop();
@@ -0,0 +1 @@
1
+ export default function updateRequiredFields(dir: string): Promise<void>;
@@ -0,0 +1,24 @@
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 updateRequiredFields(dir) {
11
+ const fieldsToUpdate = (0, read_file_1.default)('fields', dir)
12
+ .filter(field => { var _a, _b; return field.meta.required === true || ((_a = field.schema) === null || _a === void 0 ? void 0 : _a.is_nullable) === false || ((_b = field.schema) === null || _b === void 0 ? void 0 : _b.is_unique) === true; });
13
+ core_1.ux.action.start(core_1.ux.colorize(constants_1.DIRECTUS_PINK, `Updating ${fieldsToUpdate.length} fields to required`));
14
+ for await (const field of fieldsToUpdate) {
15
+ try {
16
+ await sdk_2.api.client.request((0, sdk_1.updateField)(field.collection, field.field, { meta: { ...field.meta }, schema: { ...field.schema } }));
17
+ }
18
+ catch (error) {
19
+ (0, catch_error_1.default)(error);
20
+ }
21
+ }
22
+ core_1.ux.action.stop();
23
+ }
24
+ exports.default = updateRequiredFields;
package/dist/lib/sdk.d.ts CHANGED
@@ -1,13 +1,31 @@
1
1
  import type { AuthenticationClient, RestClient } from '@directus/sdk';
2
2
  export interface Schema {
3
- any: any;
3
+ }
4
+ export declare class DirectusError extends Error {
5
+ errors: Array<{
6
+ extensions?: Record<string, unknown>;
7
+ message: string;
8
+ }>;
9
+ headers: Headers;
10
+ message: string;
11
+ response: Response;
12
+ status: number;
13
+ constructor(response: Response);
14
+ formatError(): string;
15
+ parseErrors(): Promise<void>;
4
16
  }
5
17
  declare class Api {
6
18
  client: (RestClient<Schema> & AuthenticationClient<Schema>) | undefined;
19
+ private authData;
7
20
  private limiter;
8
21
  constructor();
22
+ getToken(): null | string;
9
23
  initialize(url: string): void;
10
- setAuthToken(token: string): void;
24
+ login(email: string, password: string): Promise<void>;
25
+ loginWithToken(token: string): Promise<void>;
26
+ logout(): Promise<void>;
27
+ refreshToken(): Promise<void>;
28
+ private enhancedFetch;
11
29
  }
12
30
  declare const api: Api;
13
31
  export { api };
package/dist/lib/sdk.js CHANGED
@@ -1,32 +1,147 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.api = void 0;
3
+ exports.api = exports.DirectusError = void 0;
4
4
  const tslib_1 = require("tslib");
5
5
  const sdk_1 = require("@directus/sdk");
6
+ const core_1 = require("@oclif/core");
6
7
  const bottleneck_1 = tslib_1.__importDefault(require("bottleneck"));
8
+ class DirectusError extends Error {
9
+ constructor(response) {
10
+ super(response.statusText);
11
+ this.name = 'DirectusError';
12
+ this.headers = response.headers;
13
+ this.status = response.status;
14
+ this.response = response;
15
+ this.errors = [];
16
+ this.message = response.statusText;
17
+ }
18
+ formatError() {
19
+ if (this.errors.length === 0) {
20
+ return `Directus Error: ${this.message} (Status: ${this.status})`;
21
+ }
22
+ const { extensions, message } = this.errors[0];
23
+ let formattedError = `Directus Error: ${message.trim()} (Status: ${this.status})`;
24
+ if (extensions) {
25
+ formattedError += ` ${JSON.stringify(extensions)}`;
26
+ }
27
+ return formattedError;
28
+ }
29
+ async parseErrors() {
30
+ try {
31
+ const data = await this.response.json();
32
+ if (data && Array.isArray(data.errors)) {
33
+ this.errors = data.errors;
34
+ this.message = this.formatError();
35
+ }
36
+ }
37
+ catch {
38
+ // If parsing fails, keep the errors array empty
39
+ }
40
+ }
41
+ }
42
+ exports.DirectusError = DirectusError;
7
43
  class Api {
8
44
  constructor() {
45
+ this.authData = null;
9
46
  this.limiter = new bottleneck_1.default({
10
- maxConcurrent: 10, // Max 10 concurrent requests
11
- minTime: 100, // 100ms between requests
12
- retryCount: 3, // Retry failed requests up to 3 times
13
- retryDelay: 3000, // Wait 3 seconds between retries
47
+ maxConcurrent: 10,
48
+ minTime: 100, // Ensure at least 100ms between requests
49
+ reservoir: 50, // Reservoir to handle the default rate limiter of 50 requests per second
50
+ reservoirRefreshAmount: 50,
51
+ reservoirRefreshInterval: 1000, // Refill 50 requests every 1 second
52
+ retryCount: 3, // Retry a maximum of 3 times
53
+ });
54
+ this.limiter.on('failed', async (error, jobInfo) => {
55
+ var _a;
56
+ if (error instanceof DirectusError) {
57
+ const retryAfter = (_a = error.headers) === null || _a === void 0 ? void 0 : _a.get('Retry-After');
58
+ const statusCode = error.status;
59
+ if (statusCode === 429) {
60
+ const delay = retryAfter ? Number.parseInt(retryAfter, 10) * 1000 : 60000;
61
+ core_1.ux.log(`${core_1.ux.colorize('dim', '--')} Rate limited. Retrying after ${delay}ms`);
62
+ return delay;
63
+ }
64
+ if (statusCode === 503) {
65
+ const delay = retryAfter ? Number.parseInt(retryAfter, 10) * 1000 : 5000;
66
+ core_1.ux.log(`${core_1.ux.colorize('dim', '--')} Server under pressure. Retrying after ${delay}ms`);
67
+ return delay;
68
+ }
69
+ // If the status code is 400 or 401, we don't want to retry
70
+ if (statusCode === 400 || statusCode === 401) {
71
+ return;
72
+ }
73
+ }
74
+ // For other errors, use exponential backoff, but only if we haven't exceeded retryCount
75
+ if (jobInfo.retryCount < 3) {
76
+ const delay = Math.min(1000 * 2 ** jobInfo.retryCount, 30000);
77
+ core_1.ux.log(`${core_1.ux.colorize('dim', '--')} Request failed. Retrying after ${delay}ms`);
78
+ return delay;
79
+ }
80
+ core_1.ux.log(`${core_1.ux.colorize('dim', '--')} Max retries reached, not retrying further`);
81
+ });
82
+ this.limiter.on('retry', (error, jobInfo) => {
83
+ core_1.ux.log(`${core_1.ux.colorize('dim', '--')} Retrying job (attempt ${jobInfo.retryCount + 1})`);
84
+ });
85
+ this.limiter.on('depleted', empty => {
86
+ if (empty) {
87
+ core_1.ux.log(`${core_1.ux.colorize('dim', '--')} Rate limit quota depleted. Requests will be queued.`);
88
+ }
14
89
  });
15
90
  }
91
+ getToken() {
92
+ var _a, _b;
93
+ return (_b = (_a = this.authData) === null || _a === void 0 ? void 0 : _a.access_token) !== null && _b !== void 0 ? _b : null;
94
+ }
16
95
  initialize(url) {
17
96
  this.client = (0, sdk_1.createDirectus)(url, {
18
97
  globals: {
19
- fetch: (...args) => this.limiter.schedule(() => fetch(...args)),
98
+ fetch: this.limiter.wrap(this.enhancedFetch),
20
99
  },
21
100
  })
22
101
  .with((0, sdk_1.rest)())
23
- .with((0, sdk_1.authentication)());
102
+ .with((0, sdk_1.authentication)('json', {
103
+ autoRefresh: true,
104
+ storage: {
105
+ get: () => this.authData,
106
+ set: data => {
107
+ this.authData = data;
108
+ },
109
+ },
110
+ }));
24
111
  }
25
- setAuthToken(token) {
112
+ async login(email, password) {
26
113
  if (!this.client) {
27
114
  throw new Error('API client is not initialized. Call initialize() first.');
28
115
  }
29
- this.client.setToken(token);
116
+ await this.client.login(email, password);
117
+ }
118
+ async loginWithToken(token) {
119
+ if (!this.client) {
120
+ throw new Error('API client is not initialized. Call initialize() first.');
121
+ }
122
+ await this.client.setToken(token);
123
+ }
124
+ async logout() {
125
+ if (!this.client) {
126
+ throw new Error('API client is not initialized. Call initialize() first.');
127
+ }
128
+ await this.client.logout();
129
+ this.authData = null;
130
+ }
131
+ async refreshToken() {
132
+ if (!this.client) {
133
+ throw new Error('API client is not initialized. Call initialize() first.');
134
+ }
135
+ await this.client.refresh();
136
+ }
137
+ async enhancedFetch(...args) {
138
+ const response = await fetch(...args);
139
+ if (!response.ok) {
140
+ const error = new DirectusError(response);
141
+ await error.parseErrors();
142
+ throw error;
143
+ }
144
+ return response;
30
145
  }
31
146
  }
32
147
  const api = new Api();
@@ -1,2 +1,28 @@
1
+ interface AuthFlags {
2
+ directusToken?: string;
3
+ directusUrl: string;
4
+ userEmail?: string;
5
+ userPassword?: string;
6
+ }
7
+ /**
8
+ * Get the Directus URL from the user
9
+ * @returns The Directus URL
10
+ */
1
11
  export declare function getDirectusUrl(): Promise<string>;
12
+ /**
13
+ * Get the Directus token from the user
14
+ * @param directusUrl - The Directus URL
15
+ * @returns The Directus token
16
+ */
2
17
  export declare function getDirectusToken(directusUrl: string): Promise<string>;
18
+ /**
19
+ * Initialize the Directus API with the provided flags
20
+ * @param flags - The validated ApplyFlags
21
+ */
22
+ export declare function initializeDirectusApi(flags: AuthFlags): Promise<void>;
23
+ /**
24
+ * Validate the authentication flags
25
+ * @param flags - The AuthFlags
26
+ */
27
+ export declare function validateAuthFlags(flags: AuthFlags): void;
28
+ export {};