directus-template-cli 0.5.0-beta.2 → 0.5.0-beta.20

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 (66) hide show
  1. package/README.md +176 -15
  2. package/dist/commands/apply.d.ts +41 -18
  3. package/dist/commands/apply.js +113 -122
  4. package/dist/commands/extract.d.ts +29 -1
  5. package/dist/commands/extract.js +76 -51
  6. package/dist/flags/common.d.ts +7 -0
  7. package/dist/flags/common.js +41 -0
  8. package/dist/lib/constants.d.ts +3 -0
  9. package/dist/lib/constants.js +6 -0
  10. package/dist/lib/extract/extract-access.js +5 -3
  11. package/dist/lib/extract/extract-assets.d.ts +0 -414
  12. package/dist/lib/extract/extract-assets.js +29 -25
  13. package/dist/lib/extract/extract-collections.js +5 -4
  14. package/dist/lib/extract/extract-content.d.ts +0 -2
  15. package/dist/lib/extract/extract-content.js +16 -15
  16. package/dist/lib/extract/extract-dashboards.js +8 -6
  17. package/dist/lib/extract/extract-extensions.js +5 -3
  18. package/dist/lib/extract/extract-fields.js +5 -4
  19. package/dist/lib/extract/extract-files.js +5 -3
  20. package/dist/lib/extract/extract-flows.js +8 -6
  21. package/dist/lib/extract/extract-folders.js +5 -3
  22. package/dist/lib/extract/extract-permissions.js +5 -3
  23. package/dist/lib/extract/extract-policies.js +5 -3
  24. package/dist/lib/extract/extract-presets.js +5 -3
  25. package/dist/lib/extract/extract-relations.js +5 -3
  26. package/dist/lib/extract/extract-roles.js +5 -3
  27. package/dist/lib/extract/extract-schema.js +6 -8
  28. package/dist/lib/extract/extract-settings.js +5 -3
  29. package/dist/lib/extract/extract-translations.js +6 -6
  30. package/dist/lib/extract/extract-users.js +6 -6
  31. package/dist/lib/load/apply-flags.d.ts +22 -0
  32. package/dist/lib/load/apply-flags.js +67 -0
  33. package/dist/lib/load/index.js +9 -5
  34. package/dist/lib/load/load-access.js +47 -41
  35. package/dist/lib/load/load-collections.js +61 -17
  36. package/dist/lib/load/load-dashboards.js +30 -30
  37. package/dist/lib/load/load-data.js +47 -11
  38. package/dist/lib/load/load-extensions.js +49 -43
  39. package/dist/lib/load/load-files.js +44 -51
  40. package/dist/lib/load/load-flows.d.ts +1 -1
  41. package/dist/lib/load/load-flows.js +44 -38
  42. package/dist/lib/load/load-folders.js +34 -35
  43. package/dist/lib/load/load-permissions.js +15 -17
  44. package/dist/lib/load/load-policies.js +23 -21
  45. package/dist/lib/load/load-presets.js +27 -26
  46. package/dist/lib/load/load-relations.js +19 -18
  47. package/dist/lib/load/load-roles.js +45 -45
  48. package/dist/lib/load/load-settings.js +39 -2
  49. package/dist/lib/load/load-translations.js +24 -24
  50. package/dist/lib/load/load-users.js +44 -34
  51. package/dist/lib/load/update-required-fields.d.ts +1 -0
  52. package/dist/lib/load/update-required-fields.js +24 -0
  53. package/dist/lib/sdk.d.ts +20 -2
  54. package/dist/lib/sdk.js +124 -9
  55. package/dist/lib/utils/auth.d.ts +26 -0
  56. package/dist/lib/utils/auth.js +48 -4
  57. package/dist/lib/utils/catch-error.d.ts +15 -2
  58. package/dist/lib/utils/catch-error.js +31 -25
  59. package/dist/lib/utils/get-template.d.ts +1 -0
  60. package/dist/lib/utils/get-template.js +42 -1
  61. package/dist/lib/utils/read-file.js +2 -1
  62. package/dist/lib/utils/read-templates.js +4 -2
  63. package/oclif.manifest.json +74 -28
  64. package/package.json +2 -2
  65. package/dist/lib/interfaces.d.ts +0 -8
  66. package/dist/lib/interfaces.js +0 -2
@@ -3,38 +3,38 @@ 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
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
  async function loadTranslations(dir) {
10
- core_1.ux.action.start('Loading translations');
11
11
  const translations = (0, read_file_1.default)('translations', dir);
12
- // Fetch existing translations
13
- const existingTranslations = await sdk_2.api.client.request((0, sdk_1.readTranslations)({
14
- limit: -1,
15
- }));
16
- const existingTranslationKeys = new Set(existingTranslations.map(t => `${t.language}_${t.key}`));
17
- const newTranslations = translations.filter(t => {
18
- const key = `${t.language}_${t.key}`;
19
- if (existingTranslationKeys.has(key)) {
20
- core_1.ux.log(`Skipping existing translation: ${key}`);
21
- return false;
12
+ core_1.ux.action.start(core_1.ux.colorize(constants_1.DIRECTUS_PINK, `Loading ${translations.length} translations`));
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));
28
- core_1.ux.log(`Created ${newTranslations.length} new translations`);
34
+ else {
35
+ // ux.info('-- No new translations to create')
29
36
  }
30
- catch (error) {
31
- (0, catch_error_1.default)(error);
32
- }
33
- }
34
- else {
35
- core_1.ux.log('No new translations to create');
36
37
  }
37
38
  core_1.ux.action.stop();
38
- core_1.ux.log('Loaded translations');
39
39
  }
40
40
  exports.default = loadTranslations;
@@ -3,48 +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
8
  const catch_error_1 = tslib_1.__importDefault(require("../utils/catch-error"));
8
9
  const get_role_ids_1 = tslib_1.__importDefault(require("../utils/get-role-ids"));
9
10
  const read_file_1 = tslib_1.__importDefault(require("../utils/read-file"));
10
11
  async function loadUsers(dir) {
11
12
  const users = (0, read_file_1.default)('users', dir);
12
- core_1.ux.action.start(`Loading ${users.length} users`);
13
- const { legacyAdminRoleId, newAdminRoleId } = await (0, get_role_ids_1.default)(dir);
14
- const incomingUserEmails = users.map(user => user.email);
15
- const existingUsers = await sdk_2.api.client.request((0, sdk_1.readUsers)({
16
- filter: {
17
- email: {
18
- _in: incomingUserEmails,
19
- },
20
- },
21
- limit: -1,
22
- }));
23
- const filteredUsers = users.map(user => {
24
- // If the user is an admin, we need to change their role to the new admin role
25
- const isAdmin = user.role === legacyAdminRoleId;
26
- user.role = isAdmin ? newAdminRoleId : user.role;
27
- // Delete the unneeded fields
28
- delete user.last_page;
29
- delete user.token;
30
- delete user.policies;
31
- // Delete passwords to prevent setting to *******
32
- delete user.password;
33
- return user;
34
- });
35
- for await (const user of filteredUsers) {
36
- // If user email is null or already in use, we just delete the email key to pass validation and retain the user and any realationship data
37
- if (user.email === null || existingUsers.some(existingUser => existingUser.email === user.email)) {
38
- delete user.email;
39
- }
40
- try {
41
- await sdk_2.api.client.request((0, sdk_1.createUser)(user));
42
- }
43
- catch (error) {
44
- (0, catch_error_1.default)(error);
13
+ core_1.ux.action.start(core_1.ux.colorize(constants_1.DIRECTUS_PINK, `Loading ${users.length} users`));
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
+ }
45
56
  }
46
57
  }
47
58
  core_1.ux.action.stop();
48
- core_1.ux.log('Loaded users');
49
59
  }
50
60
  exports.default = loadUsers;
@@ -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 {};
@@ -1,12 +1,16 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.getDirectusToken = exports.getDirectusUrl = void 0;
3
+ exports.validateAuthFlags = exports.initializeDirectusApi = exports.getDirectusToken = exports.getDirectusUrl = 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
7
  const sdk_2 = require("../sdk");
8
8
  const catch_error_1 = tslib_1.__importDefault(require("./catch-error"));
9
9
  const validate_url_1 = tslib_1.__importDefault(require("./validate-url"));
10
+ /**
11
+ * Get the Directus URL from the user
12
+ * @returns The Directus URL
13
+ */
10
14
  async function getDirectusUrl() {
11
15
  const directusUrl = await core_1.ux.prompt('What is your Directus URL?', { default: 'http://localhost:8055' });
12
16
  // Validate URL
@@ -18,13 +22,17 @@ async function getDirectusUrl() {
18
22
  return directusUrl;
19
23
  }
20
24
  exports.getDirectusUrl = getDirectusUrl;
25
+ /**
26
+ * Get the Directus token from the user
27
+ * @param directusUrl - The Directus URL
28
+ * @returns The Directus token
29
+ */
21
30
  async function getDirectusToken(directusUrl) {
22
31
  const directusToken = await core_1.ux.prompt('What is your Directus Admin Token?');
23
- // Validate token
32
+ // Validate token by fetching the user
24
33
  try {
25
- sdk_2.api.setAuthToken(directusToken);
34
+ await sdk_2.api.loginWithToken(directusToken);
26
35
  const response = await sdk_2.api.client.request((0, sdk_1.readMe)());
27
- core_1.ux.log(`Logged in as ${response.first_name} ${response.last_name}`);
28
36
  return directusToken;
29
37
  }
30
38
  catch (error) {
@@ -39,3 +47,39 @@ async function getDirectusToken(directusUrl) {
39
47
  }
40
48
  }
41
49
  exports.getDirectusToken = getDirectusToken;
50
+ /**
51
+ * Initialize the Directus API with the provided flags
52
+ * @param flags - The validated ApplyFlags
53
+ */
54
+ async function initializeDirectusApi(flags) {
55
+ sdk_2.api.initialize(flags.directusUrl);
56
+ try {
57
+ if (flags.directusToken) {
58
+ await sdk_2.api.loginWithToken(flags.directusToken);
59
+ }
60
+ else if (flags.userEmail && flags.userPassword) {
61
+ await sdk_2.api.login(flags.userEmail, flags.userPassword);
62
+ }
63
+ const response = await sdk_2.api.client.request((0, sdk_1.readMe)());
64
+ core_1.ux.log(`-- Logged in as ${response.first_name} ${response.last_name}`);
65
+ }
66
+ catch {
67
+ (0, catch_error_1.default)('-- Unable to authenticate with the provided credentials. Please check your credentials.', {
68
+ fatal: true,
69
+ });
70
+ }
71
+ }
72
+ exports.initializeDirectusApi = initializeDirectusApi;
73
+ /**
74
+ * Validate the authentication flags
75
+ * @param flags - The AuthFlags
76
+ */
77
+ function validateAuthFlags(flags) {
78
+ if (!flags.directusUrl) {
79
+ core_1.ux.error('Directus URL is required.');
80
+ }
81
+ if (!flags.directusToken && (!flags.userEmail || !flags.userPassword)) {
82
+ core_1.ux.error('Either Directus token or email and password are required.');
83
+ }
84
+ }
85
+ exports.validateAuthFlags = validateAuthFlags;
@@ -1,6 +1,19 @@
1
- interface Options {
1
+ /**
2
+ * Options for configuring the error handler behavior.
3
+ */
4
+ interface ErrorHandlerOptions {
5
+ /** Additional context to be included in the error log. */
2
6
  context?: Record<string, any>;
7
+ /** If true, the error will be treated as fatal and the process will exit. */
3
8
  fatal?: boolean;
9
+ /** If true, the error will be logged to a file. */
10
+ logToFile?: boolean;
4
11
  }
5
- export default function catchError(error: unknown, options?: Options, logToFile?: boolean): void;
12
+ /**
13
+ * Handles errors by formatting them and optionally logging to console and file.
14
+ * @param error - The error to be handled.
15
+ * @param options - Configuration options for error handling.
16
+ * @returns void
17
+ */
18
+ export default function catchError(error: unknown, options?: ErrorHandlerOptions): void;
6
19
  export {};
@@ -1,35 +1,41 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  const core_1 = require("@oclif/core");
4
- const logger_1 = require("./logger");
5
- function catchError(error, options = {}, logToFile = true) {
6
- const errorMessage = isDirectusError(error) ? formatDirectusError(error)
7
- : (error instanceof Error ? formatGenericError(error)
8
- : `Unknown error: ${JSON.stringify(error)}`);
9
- const contextString = options.context
10
- ? Object.entries(options.context)
11
- .map(([key, value]) => `${key}: ${JSON.stringify(value)}`)
12
- .join(', ') : '';
4
+ const sdk_1 = require("../sdk");
5
+ const logger_1 = require("../utils/logger");
6
+ /**
7
+ * Handles errors by formatting them and optionally logging to console and file.
8
+ * @param error - The error to be handled.
9
+ * @param options - Configuration options for error handling.
10
+ * @returns void
11
+ */
12
+ function catchError(error, options = {}) {
13
+ const { context = {}, fatal = false, logToFile = true } = options;
14
+ let errorMessage;
15
+ if (error instanceof sdk_1.DirectusError) {
16
+ errorMessage = error.message;
17
+ }
18
+ else if (error instanceof Error) {
19
+ errorMessage = `Error: ${error.message}`;
20
+ }
21
+ else {
22
+ errorMessage = `Unknown error: ${JSON.stringify(error)}`;
23
+ }
24
+ // Format the error message with context if provided
13
25
  const formattedMessage = [
14
- contextString && `Context: ${contextString}`,
15
26
  errorMessage,
27
+ Object.keys(context).length > 0 && `Context: ${JSON.stringify(context)}`,
16
28
  ].filter(Boolean).join('\n');
17
- options.fatal ? core_1.ux.error(formattedMessage) : core_1.ux.warn(formattedMessage);
29
+ // Log the error message to the console with the appropriate color
30
+ if (fatal) {
31
+ // ux.error exits the process with a non-zero code
32
+ core_1.ux.error(formattedMessage);
33
+ }
34
+ else {
35
+ core_1.ux.warn(formattedMessage);
36
+ }
18
37
  if (logToFile) {
19
- logger_1.logger.log('error', errorMessage, options.context);
38
+ logger_1.logger.log('error', errorMessage, context);
20
39
  }
21
40
  }
22
41
  exports.default = catchError;
23
- function isDirectusError(error) {
24
- // @ts-ignore
25
- return error && Array.isArray(error.errors) && error.errors.length > 0;
26
- }
27
- function formatDirectusError(error) {
28
- var _a;
29
- const status = ((_a = error.response) === null || _a === void 0 ? void 0 : _a.status) || 'Unknown';
30
- const { extensions, message } = error.errors[0];
31
- return `Directus Error: ${message.trim()}${extensions ? ` (${JSON.stringify(extensions)})` : ''} (Status: ${status})`;
32
- }
33
- function formatGenericError(error) {
34
- return `Error: ${error.message}`;
35
- }
@@ -4,5 +4,6 @@ interface Template {
4
4
  }
5
5
  export declare function getCommunityTemplates(): Promise<Template[]>;
6
6
  export declare function getLocalTemplate(localTemplateDir: string): Promise<Template>;
7
+ export declare function getInteractiveLocalTemplate(localTemplateDir: string): Promise<Template[]>;
7
8
  export declare function getGithubTemplate(ghTemplateUrl: string): Promise<Template>;
8
9
  export {};
@@ -1,8 +1,9 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.getGithubTemplate = exports.getLocalTemplate = exports.getCommunityTemplates = void 0;
3
+ exports.getGithubTemplate = exports.getInteractiveLocalTemplate = exports.getLocalTemplate = exports.getCommunityTemplates = void 0;
4
4
  const tslib_1 = require("tslib");
5
5
  const giget_1 = require("giget");
6
+ const node_fs_1 = tslib_1.__importDefault(require("node:fs"));
6
7
  const node_path_1 = tslib_1.__importDefault(require("node:path"));
7
8
  const path_1 = tslib_1.__importDefault(require("./path"));
8
9
  const read_templates_1 = require("./read-templates");
@@ -33,6 +34,46 @@ async function getLocalTemplate(localTemplateDir) {
33
34
  return (0, read_templates_1.readTemplate)(resolvedDir);
34
35
  }
35
36
  exports.getLocalTemplate = getLocalTemplate;
37
+ async function getInteractiveLocalTemplate(localTemplateDir) {
38
+ const resolvedDir = (0, path_1.default)(localTemplateDir);
39
+ if (!resolvedDir) {
40
+ throw new Error('Directory does not exist.');
41
+ }
42
+ const directTemplate = await (0, read_templates_1.readTemplate)(resolvedDir);
43
+ if (directTemplate) {
44
+ return [directTemplate];
45
+ }
46
+ const templates = await (0, read_templates_1.readAllTemplates)(resolvedDir);
47
+ if (templates.length === 0) {
48
+ // If no templates found, search nested directories
49
+ const nestedTemplates = await findNestedTemplates(resolvedDir, 2);
50
+ if (nestedTemplates.length === 0) {
51
+ throw new Error('No valid templates found in the specified directory or its subdirectories.');
52
+ }
53
+ return nestedTemplates;
54
+ }
55
+ return templates;
56
+ }
57
+ exports.getInteractiveLocalTemplate = getInteractiveLocalTemplate;
58
+ async function findNestedTemplates(dir, depth) {
59
+ if (depth === 0)
60
+ return [];
61
+ const templates = [];
62
+ const entries = await node_fs_1.default.promises.readdir(dir, { withFileTypes: true });
63
+ for (const entry of entries) {
64
+ if (entry.isDirectory()) {
65
+ const fullPath = node_path_1.default.join(dir, entry.name);
66
+ const dirTemplates = await (0, read_templates_1.readAllTemplates)(fullPath);
67
+ templates.push(...dirTemplates);
68
+ if (dirTemplates.length === 0 && depth > 1) {
69
+ // If no templates found and we can go deeper, search subdirectories
70
+ const nestedTemplates = await findNestedTemplates(fullPath, depth - 1);
71
+ templates.push(...nestedTemplates);
72
+ }
73
+ }
74
+ }
75
+ return templates;
76
+ }
36
77
  async function getGithubTemplate(ghTemplateUrl) {
37
78
  try {
38
79
  const ghString = await (0, transform_github_url_1.transformGitHubUrl)(ghTemplateUrl);
@@ -3,10 +3,11 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  const tslib_1 = require("tslib");
4
4
  const node_fs_1 = tslib_1.__importDefault(require("node:fs"));
5
5
  const node_path_1 = tslib_1.__importDefault(require("node:path"));
6
+ const catch_error_1 = tslib_1.__importDefault(require("./catch-error"));
6
7
  function readFile(file, dir) {
7
8
  const filePath = node_path_1.default.join(dir, `${file}.json`); // Use path.join for proper path resolution
8
9
  if (!node_fs_1.default.existsSync(filePath)) {
9
- throw new Error(`File not found: ${filePath}`); // Improved error handling
10
+ (0, catch_error_1.default)(`File not found: ${filePath}`);
10
11
  }
11
12
  const fileContents = node_fs_1.default.readFileSync(filePath, 'utf8');
12
13
  const obj = JSON.parse(fileContents);