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.
- package/README.md +186 -17
- package/dist/commands/apply.d.ts +25 -3
- package/dist/commands/apply.js +78 -121
- package/dist/commands/extract.d.ts +29 -1
- package/dist/commands/extract.js +72 -50
- package/dist/flags/common.d.ts +7 -0
- package/dist/flags/common.js +41 -0
- package/dist/lib/extract/extract-access.js +5 -3
- package/dist/lib/extract/extract-assets.d.ts +0 -414
- package/dist/lib/extract/extract-assets.js +29 -25
- package/dist/lib/extract/extract-collections.js +5 -4
- package/dist/lib/extract/extract-content.d.ts +0 -2
- package/dist/lib/extract/extract-content.js +16 -15
- package/dist/lib/extract/extract-dashboards.js +8 -6
- package/dist/lib/extract/extract-extensions.js +5 -3
- package/dist/lib/extract/extract-fields.js +4 -2
- package/dist/lib/extract/extract-files.js +5 -3
- package/dist/lib/extract/extract-flows.js +8 -6
- package/dist/lib/extract/extract-folders.js +5 -3
- package/dist/lib/extract/extract-permissions.js +5 -3
- package/dist/lib/extract/extract-policies.js +5 -3
- package/dist/lib/extract/extract-presets.js +5 -3
- package/dist/lib/extract/extract-relations.js +5 -3
- package/dist/lib/extract/extract-roles.js +5 -3
- package/dist/lib/extract/extract-schema.js +6 -8
- package/dist/lib/extract/extract-settings.js +5 -3
- package/dist/lib/extract/extract-translations.js +6 -6
- package/dist/lib/extract/extract-users.js +6 -6
- package/dist/lib/load/apply-flags.d.ts +22 -0
- package/dist/lib/load/apply-flags.js +67 -0
- package/dist/lib/load/index.js +9 -5
- package/dist/lib/load/load-access.js +45 -37
- package/dist/lib/load/load-collections.js +20 -2
- package/dist/lib/load/load-dashboards.js +27 -25
- package/dist/lib/load/load-data.js +3 -3
- package/dist/lib/load/load-files.js +42 -46
- package/dist/lib/load/load-flows.js +27 -30
- package/dist/lib/load/load-folders.js +32 -30
- package/dist/lib/load/load-permissions.js +14 -12
- package/dist/lib/load/load-policies.js +21 -19
- package/dist/lib/load/load-presets.js +25 -23
- package/dist/lib/load/load-relations.js +17 -15
- package/dist/lib/load/load-roles.js +43 -41
- package/dist/lib/load/load-translations.js +22 -20
- package/dist/lib/load/load-users.js +42 -39
- package/dist/lib/load/update-required-fields.d.ts +1 -0
- package/dist/lib/load/update-required-fields.js +24 -0
- package/dist/lib/sdk.d.ts +20 -2
- package/dist/lib/sdk.js +124 -9
- package/dist/lib/utils/auth.d.ts +26 -0
- package/dist/lib/utils/auth.js +48 -4
- package/dist/lib/utils/catch-error.d.ts +15 -2
- package/dist/lib/utils/catch-error.js +31 -25
- package/dist/lib/utils/get-template.js +0 -1
- package/oclif.manifest.json +74 -28
- package/package.json +2 -2
- package/dist/lib/interfaces.d.ts +0 -8
- 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
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
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
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
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
|
-
|
|
23
|
-
|
|
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
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
const
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
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
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
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
|
-
|
|
36
|
-
|
|
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
|
-
|
|
57
|
-
|
|
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
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
const
|
|
20
|
-
|
|
21
|
-
|
|
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
|
-
|
|
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
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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,
|
|
11
|
-
minTime: 100, // 100ms between requests
|
|
12
|
-
|
|
13
|
-
|
|
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:
|
|
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
|
-
|
|
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.
|
|
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();
|
package/dist/lib/utils/auth.d.ts
CHANGED
|
@@ -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 {};
|