directus-template-cli 0.5.0-beta.12 → 0.5.0-beta.13

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 CHANGED
@@ -5,13 +5,9 @@ A CLI tool to make applying or extracting Directus "templates" a little easier..
5
5
  **Notes:**
6
6
 
7
7
  - This is a beta release. It is recommended for use on POC, demo, or greenfield projects only. When applying templates, you should always backup your project/database before applying a template.
8
-
9
8
  - ⚠️ Known issues with using MySQL currently. We highly recommend using PostgreSQL or SQLite for your database provider. If you're using PostgreSQL in production, we recommend using PostgreSQL in local development as well.
10
9
  - If you are extracting or applying from a remote source, the script can take quite a while depending on the "size" of your instance (how many collections, how many items in each collection, number and size of assets, etc). The script applies a strict rate limit of 10 requests per second using bottleneck.
11
-
12
- Breaking Changes in v0.5.0
13
-
14
- - The CLI is now compatible with Directus 11. If you need to apply or extract to an instance of Directus 10, you can use v0.4.0 of the CLI. `npx directus-template-cli@0.4 extract` or `npx directus-template-cli@0.4 apply`.
10
+ - As of v0.5.0, the CLI is compatible with Directus 11 and up. If you need to apply or extract to an instance of Directus 10, you can use v0.4.0 of the CLI. `npx directus-template-cli@0.4 extract` or `npx directus-template-cli@0.4 apply`.
15
11
 
16
12
  Using the @latest tag ensures you're receiving the latest version of the packaged templates with the CLI. You can review [the specific versions on NPM](https://www.npmjs.com/package/directus-template-cli) and use @{version} syntax to apply the templates included with that version.
17
13
 
@@ -25,7 +21,7 @@ Using the @latest tag ensures you're receiving the latest version of the package
25
21
  4. Run the following command on the terminal and follow the prompts.
26
22
 
27
23
  ```
28
- $ npx directus-template-cli@latest apply
24
+ npx directus-template-cli@latest apply
29
25
  ```
30
26
 
31
27
  You can choose from our community maintained templates or you can also choose a template from a local directory or a public GitHub repository.
@@ -33,13 +29,13 @@ You can choose from our community maintained templates or you can also choose a
33
29
 
34
30
  ### Programmatic Mode
35
31
 
36
- For CI/CD pipelines or automated scripts, you can use the programmatic mode:
32
+ By default, the CLI will run in interactive mode. For CI/CD pipelines or automated scripts, you can use the programmatic mode:
37
33
 
38
34
 
39
35
  Using a token:
40
36
 
41
37
  ```
42
- $ npx directus-template-cli@latest apply -p --directusUrl="http://localhost:8055" --directusToken="admin-token-here" --templateLocation="./my-template" --templateType="local"
38
+ npx directus-template-cli@latest apply -p --directusUrl="http://localhost:8055" --directusToken="admin-token-here" --templateLocation="./my-template" --templateType="local"
43
39
  ```
44
40
 
45
41
  Using email/password:
@@ -62,7 +58,7 @@ Available flags for programmatic mode:
62
58
  - `--userEmail`: Email for Directus authentication (required if not using token)
63
59
  - `--userPassword`: Password for Directus authentication (required if not using token)
64
60
  - `--templateLocation`: Location of the template to apply (required)
65
- - `--templateType`: Type of template to apply. Options: community, local, github (required)
61
+ - `--templateType`: Type of template to apply. Options: community, local, github. Defaults to `local`.
66
62
  - `--partial`: Enable partial template application
67
63
  - `--content`: Load Content (data)
68
64
  - `--dashboards`: Load Dashboards
@@ -77,7 +73,7 @@ Available flags for programmatic mode:
77
73
  When using `--partial`, you can also use `--no` flags to exclude specific components from being applied. For example:
78
74
 
79
75
  ```
80
- $ npx directus-template-cli@latest apply -p --directusUrl="http://localhost:8055" --userEmail="admin@example.com" --userPassword="your-password" --templateLocation="./my-template" --templateType="local" --partial --no-content --no-users
76
+ npx directus-template-cli@latest apply -p --directusUrl="http://localhost:8055" --userEmail="admin@example.com" --userPassword="your-password" --templateLocation="./my-template" --templateType="local" --partial --no-content --no-users
81
77
  ```
82
78
 
83
79
  This command will apply the template but exclude content and users. Available `--no` flags include:
@@ -109,13 +105,43 @@ When applying templates, certain components have dependencies on others. Here ar
109
105
  When using the `--partial` flag, keep these dependencies in mind. For example:
110
106
 
111
107
  ```
112
- $ npx directus-template-cli@latest apply -p --directusUrl="http://localhost:8055" --directusToken="admin-token-here" --templateLocation="./my-template" --templateType="local" --partial --users
108
+ npx directus-template-cli@latest apply -p --directusUrl="http://localhost:8055" --directusToken="admin-token-here" --templateLocation="./my-template" --templateType="local" --partial --users
113
109
  ```
114
110
 
115
111
  This command will automatically include `--permissions` and `--schema` along with `--users`, even if not explicitly specified.
116
112
 
117
113
  If you use `--no-` flags, be cautious about excluding dependencies. For instance, using `--no-schema` while including `--content` may lead to errors or incomplete application of the template.
118
114
 
115
+ #### Using Environment Variables
116
+
117
+ You can also pass flags as environment variables. This can be useful for CI/CD pipelines or when you want to avoid exposing sensitive information in command-line arguments. Here are the available environment variables:
118
+
119
+ - `TARGET_DIRECTUS_URL`: Equivalent to `--directusUrl`
120
+ - `TARGET_DIRECTUS_TOKEN`: Equivalent to `--directusToken`
121
+ - `TARGET_DIRECTUS_EMAIL`: Equivalent to `--userEmail`
122
+ - `TARGET_DIRECTUS_PASSWORD`: Equivalent to `--userPassword`
123
+ - `TEMPLATE_LOCATION`: Equivalent to `--templateLocation`
124
+ - `TEMPLATE_TYPE`: Equivalent to `--templateType`
125
+ -
126
+
127
+ ### Existing Data
128
+
129
+ You can apply a template to an existing Directus instance. This is nice because you can have smaller templates that you can "compose" for various use cases. The CLI tries to be smart about existing items in the target Directus instance. But mileage may vary depending on the size and complexity of the template and the existing instance.
130
+
131
+ **System Collections**
132
+
133
+ In most of the system collections (collections,roles, permissions, etc.), if an item with the same identifier already exists, it will be typically be SKIPPED vs overwritten.
134
+
135
+ Exceptions:
136
+
137
+ - directus_settings: The CLI attempts to merge the template's project settings with the existing settings in the target instance. Using the existing settings as a base and updating them with the values from the template. This should prevent overwriting branding, themes, and other customizations.
138
+
139
+ **Your Collections:**
140
+
141
+ For data in your own user-created collections, if an item has the same primary key, the data will be overwritten with the incoming data from the template.
142
+
143
+ ---
144
+
119
145
  ## Extracting a Template
120
146
 
121
147
  The CLI can also extract a template from a Directus instance so that it can be applied to other instances.
@@ -133,7 +159,7 @@ npx directus-template-cli@latest extract
133
159
 
134
160
  ### Programmatic Mode
135
161
 
136
- For CI/CD pipelines or automated scripts, you can use the programmatic mode:
162
+ By default, the CLI will run in interactive mode. For CI/CD pipelines or automated scripts, you can use the programmatic mode:
137
163
 
138
164
  Using a token:
139
165
 
@@ -156,12 +182,20 @@ Available flags for programmatic mode:
156
182
  - `--templateLocation`: Directory to extract the template to (required)
157
183
  - `--templateName`: Name of the template (required)
158
184
 
185
+ #### Using Environment Variables
186
+
187
+ Similar to the Apply command, you can use environment variables for the Extract command as well:
188
+
189
+ - `SOURCE_DIRECTUS_URL`: Equivalent to `--directusUrl`
190
+ - `SOURCE_DIRECTUS_TOKEN`: Equivalent to `--directusToken`
191
+ - `SOURCE_DIRECTUS_EMAIL`: Equivalent to `--userEmail`
192
+ - `SOURCE_DIRECTUS_PASSWORD`: Equivalent to `--userPassword`
193
+ - `TEMPLATE_LOCATION`: Equivalent to `--templateLocation`
194
+
159
195
  ## Logs
160
196
 
161
197
  The Directus Template CLI logs information to a file in the `.directus-template-cli/logs` directory.
162
198
 
163
- ### Log Generation
164
-
165
199
  Logs are automatically generated for each run of the CLI. Here's how the logging system works:
166
200
  - A new log file is created for each CLI run.
167
201
  - Log files are stored in the `.directus-template-cli/logs` directory within your current working directory.
@@ -21,17 +21,31 @@ export default class ApplyCommand extends Command {
21
21
  userPassword: import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces").CustomOptions>;
22
22
  users: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
23
23
  };
24
+ /**
25
+ * MAIN
26
+ * Run the command
27
+ * @returns {Promise<void>} - Returns nothing
28
+ */
24
29
  run(): Promise<void>;
25
- private checkAtLeastOneFlagEnabled;
26
- private handleDependencies;
27
- private handlePartialFlags;
28
- private initializeDirectusApi;
29
- private redirectToDirectusPlus;
30
+ /**
31
+ * INTERACTIVE
32
+ * Run the command in interactive mode
33
+ * @param flags - The ApplyFlags
34
+ * @returns {Promise<void>} - Returns nothing
35
+ */
30
36
  private runInteractive;
37
+ /**
38
+ * PROGRAMMATIC
39
+ * Run the command in programmatic mode
40
+ * @param flags - The ApplyFlags
41
+ * @returns {Promise<void>} - Returns nothing
42
+ */
31
43
  private runProgrammatic;
44
+ /**
45
+ * INTERACTIVE
46
+ * Select a local template from the given directory
47
+ * @param localTemplateDir - The local template directory path
48
+ * @returns {Promise<Template>} - Returns the selected template
49
+ */
32
50
  private selectLocalTemplate;
33
- private setAllFlagsTrue;
34
- private setSpecificFlags;
35
- private validateFlags;
36
- private validateProgrammaticFlags;
37
51
  }
@@ -1,85 +1,36 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  const tslib_1 = require("tslib");
4
- const sdk_1 = require("@directus/sdk");
5
4
  const core_1 = require("@oclif/core");
6
5
  const inquirer = tslib_1.__importStar(require("inquirer"));
6
+ const customFlags = tslib_1.__importStar(require("../flags/common"));
7
7
  const constants_1 = require("../lib/constants");
8
+ const apply_flags_1 = require("../lib/load/apply-flags");
8
9
  const index_js_1 = tslib_1.__importDefault(require("../lib/load/index.js"));
9
- const sdk_2 = require("../lib/sdk");
10
10
  const auth_1 = require("../lib/utils/auth");
11
11
  const catch_error_1 = tslib_1.__importDefault(require("../lib/utils/catch-error"));
12
12
  const get_template_1 = require("../lib/utils/get-template");
13
13
  const logger_1 = require("../lib/utils/logger");
14
14
  const open_url_1 = tslib_1.__importDefault(require("../lib/utils/open-url"));
15
15
  class ApplyCommand extends core_1.Command {
16
- // MAIN FUNCTION
16
+ /**
17
+ * MAIN
18
+ * Run the command
19
+ * @returns {Promise<void>} - Returns nothing
20
+ */
17
21
  async run() {
18
22
  const { flags } = await this.parse(ApplyCommand);
19
23
  const typedFlags = flags;
20
24
  await (typedFlags.programmatic ? this.runProgrammatic(typedFlags) : this.runInteractive(typedFlags));
21
25
  }
22
- checkAtLeastOneFlagEnabled(validatedFlags, loadFlags) {
23
- const enabledFlags = loadFlags.filter(flag => validatedFlags[flag] === true);
24
- if (enabledFlags.length === 0) {
25
- core_1.ux.error('When using --partial, at least one component must be loaded.');
26
- }
27
- }
28
- handleDependencies(validatedFlags, flags) {
29
- if (validatedFlags.content) {
30
- validatedFlags.schema = true;
31
- validatedFlags.files = true;
32
- if (!flags.schema || !flags.files) {
33
- core_1.ux.warn('Content loading requires schema and files. Enabling schema and files flags.');
34
- }
35
- }
36
- if (validatedFlags.users) {
37
- validatedFlags.permissions = true;
38
- if (!flags.permissions) {
39
- core_1.ux.warn('User loading requires permissions. Enabling permissions flag.');
40
- }
41
- }
42
- }
43
- handlePartialFlags(validatedFlags, flags, loadFlags) {
44
- const explicitlyEnabledFlags = loadFlags.filter(flag => flags[flag] === true);
45
- const explicitlyDisabledFlags = loadFlags.filter(flag => flags[flag] === false);
46
- if (explicitlyEnabledFlags.length > 0) {
47
- this.setSpecificFlags(validatedFlags, loadFlags, explicitlyEnabledFlags, true);
48
- }
49
- else if (explicitlyDisabledFlags.length > 0) {
50
- this.setSpecificFlags(validatedFlags, loadFlags, explicitlyDisabledFlags, false);
51
- }
52
- else {
53
- this.setAllFlagsTrue(validatedFlags, loadFlags);
54
- }
55
- this.handleDependencies(validatedFlags, flags);
56
- this.checkAtLeastOneFlagEnabled(validatedFlags, loadFlags);
57
- }
58
- async initializeDirectusApi(flags) {
59
- sdk_2.api.initialize(flags.directusUrl);
60
- try {
61
- if (flags.directusToken) {
62
- await sdk_2.api.loginWithToken(flags.directusToken);
63
- }
64
- else if (flags.userEmail && flags.userPassword) {
65
- await sdk_2.api.login(flags.userEmail, flags.userPassword);
66
- }
67
- const response = await sdk_2.api.client.request((0, sdk_1.readMe)());
68
- core_1.ux.log(`-- Logged in as ${response.first_name} ${response.last_name}`);
69
- }
70
- catch {
71
- (0, catch_error_1.default)('-- Unable to authenticate with the provided credentials. Please check your credentials.', {
72
- fatal: true,
73
- });
74
- }
75
- }
76
- redirectToDirectusPlus() {
77
- (0, open_url_1.default)('https://directus.io/plus?utm_source=directus-template-cli&utm_content=apply-command');
78
- core_1.ux.log('Redirecting to Directus website.');
79
- core_1.ux.exit(0);
80
- }
26
+ /**
27
+ * INTERACTIVE
28
+ * Run the command in interactive mode
29
+ * @param flags - The ApplyFlags
30
+ * @returns {Promise<void>} - Returns nothing
31
+ */
81
32
  async runInteractive(flags) {
82
- const validatedFlags = this.validateFlags(flags);
33
+ const validatedFlags = (0, apply_flags_1.validateInteractiveFlags)(flags);
83
34
  core_1.ux.styledHeader(core_1.ux.colorize(constants_1.DIRECTUS_PURPLE, 'Directus Template CLI - Apply'));
84
35
  const templateType = await inquirer.prompt([
85
36
  {
@@ -120,7 +71,9 @@ class ApplyCommand extends core_1.Command {
120
71
  break;
121
72
  }
122
73
  case 'directus-plus': {
123
- this.redirectToDirectusPlus();
74
+ (0, open_url_1.default)('https://directus.io/plus?utm_source=directus-template-cli&utm_content=apply-command');
75
+ core_1.ux.log('Redirecting to Directus website.');
76
+ core_1.ux.exit(0);
124
77
  }
125
78
  }
126
79
  core_1.ux.log(`You selected ${core_1.ux.colorize(constants_1.DIRECTUS_PINK, template.templateName)}`);
@@ -151,7 +104,7 @@ class ApplyCommand extends core_1.Command {
151
104
  const userPassword = await core_1.ux.prompt('What is your password?');
152
105
  validatedFlags.userPassword = userPassword;
153
106
  }
154
- await this.initializeDirectusApi(validatedFlags);
107
+ await (0, auth_1.initializeDirectusApi)(validatedFlags);
155
108
  if (template) {
156
109
  core_1.ux.styledHeader(core_1.ux.colorize(constants_1.DIRECTUS_PURPLE, `Applying template - ${template.templateName} to ${directusUrl}`));
157
110
  await (0, index_js_1.default)(template.directoryPath, validatedFlags);
@@ -161,8 +114,14 @@ class ApplyCommand extends core_1.Command {
161
114
  core_1.ux.exit(0);
162
115
  }
163
116
  }
117
+ /**
118
+ * PROGRAMMATIC
119
+ * Run the command in programmatic mode
120
+ * @param flags - The ApplyFlags
121
+ * @returns {Promise<void>} - Returns nothing
122
+ */
164
123
  async runProgrammatic(flags) {
165
- const validatedFlags = this.validateFlags(flags);
124
+ const validatedFlags = (0, apply_flags_1.validateProgrammaticFlags)(flags);
166
125
  let template;
167
126
  switch (validatedFlags.templateType) {
168
127
  case 'community': {
@@ -184,7 +143,7 @@ class ApplyCommand extends core_1.Command {
184
143
  });
185
144
  }
186
145
  }
187
- await this.initializeDirectusApi(validatedFlags);
146
+ await (0, auth_1.initializeDirectusApi)(validatedFlags);
188
147
  const logMessage = `Applying template - ${template.templateName} to ${validatedFlags.directusUrl}`;
189
148
  core_1.ux.styledHeader(logMessage);
190
149
  logger_1.logger.log('info', logMessage);
@@ -194,6 +153,12 @@ class ApplyCommand extends core_1.Command {
194
153
  core_1.ux.info('Template applied successfully.');
195
154
  core_1.ux.exit(0);
196
155
  }
156
+ /**
157
+ * INTERACTIVE
158
+ * Select a local template from the given directory
159
+ * @param localTemplateDir - The local template directory path
160
+ * @returns {Promise<Template>} - Returns the selected template
161
+ */
197
162
  async selectLocalTemplate(localTemplateDir) {
198
163
  try {
199
164
  const templates = await (0, get_template_1.getInteractiveLocalTemplate)(localTemplateDir);
@@ -219,55 +184,6 @@ class ApplyCommand extends core_1.Command {
219
184
  }
220
185
  }
221
186
  }
222
- setAllFlagsTrue(flags, loadFlags) {
223
- for (const flag of loadFlags) {
224
- flags[flag] = true;
225
- }
226
- return flags;
227
- }
228
- setSpecificFlags(flags, allFlags, specificFlags, value) {
229
- for (const flag of allFlags) {
230
- flags[flag] = specificFlags.includes(flag) === value;
231
- }
232
- }
233
- validateFlags(flags) {
234
- this.validateProgrammaticFlags(flags);
235
- const loadFlags = [
236
- 'content',
237
- 'dashboards',
238
- 'extensions',
239
- 'files',
240
- 'flows',
241
- 'permissions',
242
- 'schema',
243
- 'settings',
244
- 'users',
245
- ];
246
- const validatedFlags = { ...flags };
247
- if (flags.programmatic && !flags.partial) {
248
- return this.setAllFlagsTrue(validatedFlags, loadFlags);
249
- }
250
- if (flags.partial) {
251
- this.handlePartialFlags(validatedFlags, flags, loadFlags);
252
- }
253
- else {
254
- this.setAllFlagsTrue(validatedFlags, loadFlags);
255
- }
256
- return validatedFlags;
257
- }
258
- validateProgrammaticFlags(flags) {
259
- if (!flags.programmatic)
260
- return;
261
- if (!flags.directusUrl) {
262
- core_1.ux.error('Directus URL is required for programmatic mode.');
263
- }
264
- if (!flags.directusToken && (!flags.userEmail || !flags.userPassword)) {
265
- core_1.ux.error('Either Directus token or email and password are required for programmatic mode.');
266
- }
267
- if (!flags.templateLocation) {
268
- core_1.ux.error('Template location is required for programmatic mode.');
269
- }
270
- }
271
187
  }
272
188
  ApplyCommand.description = 'Apply a template to a blank Directus instance.';
273
189
  ApplyCommand.examples = [
@@ -286,15 +202,8 @@ ApplyCommand.flags = {
286
202
  default: undefined,
287
203
  description: 'Load Dashboards (dashboards, panels)',
288
204
  }),
289
- directusToken: core_1.Flags.string({
290
- description: 'Token to use for the Directus instance',
291
- env: 'TARGET_DIRECTUS_TOKEN',
292
- exclusive: ['userEmail', 'userPassword'],
293
- }),
294
- directusUrl: core_1.Flags.string({
295
- description: 'URL of the Directus instance to apply the template to',
296
- env: 'TARGET_DIRECTUS_URL',
297
- }),
205
+ directusToken: customFlags.directusToken,
206
+ directusUrl: customFlags.directusUrl,
298
207
  extensions: core_1.Flags.boolean({
299
208
  allowNo: true,
300
209
  default: undefined,
@@ -321,12 +230,7 @@ ApplyCommand.flags = {
321
230
  description: 'Loads permissions data. Collections include: directus_roles, directus_policies, directus_access, directus_permissions.',
322
231
  summary: 'Load permissions (roles, policies, access, permissions)',
323
232
  }),
324
- programmatic: core_1.Flags.boolean({
325
- char: 'p',
326
- default: false,
327
- description: 'Run in programmatic mode (non-interactive) for use cases such as CI/CD pipelines.',
328
- summary: 'Run in programmatic mode',
329
- }),
233
+ programmatic: customFlags.programmatic,
330
234
  schema: core_1.Flags.boolean({
331
235
  allowNo: true,
332
236
  default: undefined,
@@ -337,11 +241,7 @@ ApplyCommand.flags = {
337
241
  default: undefined,
338
242
  description: 'Load settings (project settings, translations, presets)',
339
243
  }),
340
- templateLocation: core_1.Flags.string({
341
- dependsOn: ['programmatic', 'templateType'],
342
- description: 'Location of the template to apply',
343
- env: 'TEMPLATE_LOCATION',
344
- }),
244
+ templateLocation: customFlags.templateLocation,
345
245
  templateType: core_1.Flags.string({
346
246
  default: 'local',
347
247
  dependsOn: ['programmatic'],
@@ -350,18 +250,8 @@ ApplyCommand.flags = {
350
250
  options: ['community', 'local', 'github'],
351
251
  summary: 'Type of template to apply. Options: community, local, github.',
352
252
  }),
353
- userEmail: core_1.Flags.string({
354
- dependsOn: ['userPassword'],
355
- description: 'Email for Directus authentication',
356
- env: 'TARGET_DIRECTUS_EMAIL',
357
- exclusive: ['directusToken'],
358
- }),
359
- userPassword: core_1.Flags.string({
360
- dependsOn: ['userEmail'],
361
- description: 'Password for Directus authentication',
362
- env: 'TARGET_DIRECTUS_PASSWORD',
363
- exclusive: ['directusToken'],
364
- }),
253
+ userEmail: customFlags.userEmail,
254
+ userPassword: customFlags.userPassword,
365
255
  users: core_1.Flags.boolean({
366
256
  allowNo: true,
367
257
  default: undefined,
@@ -11,10 +11,36 @@ export default class ExtractCommand extends Command {
11
11
  userEmail: import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces").CustomOptions>;
12
12
  userPassword: import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces").CustomOptions>;
13
13
  };
14
+ /**
15
+ * Main run method for the ExtractCommand
16
+ * @returns {Promise<void>} - Returns nothing
17
+ */
14
18
  run(): Promise<void>;
19
+ /**
20
+ * Extracts the template to the specified directory
21
+ * @param {string} templateName - The name of the template to extract
22
+ * @param {string} directory - The directory to extract the template to
23
+ * @param {ExtractFlags} flags - The command flags
24
+ * @returns {Promise<void>} - Returns nothing
25
+ */
15
26
  private extractTemplate;
16
- private initializeDirectusApi;
27
+ /**
28
+ * Runs the interactive mode for template extraction
29
+ * @param {ExtractFlags} flags - The command flags
30
+ * @returns {Promise<void>} - Returns nothing
31
+ */
17
32
  private runInteractive;
33
+ /**
34
+ * Runs the programmatic mode for template extraction
35
+ * @param {ExtractFlags} flags - The command flags
36
+ * @returns {Promise<void>} - Returns nothing
37
+ */
18
38
  private runProgrammatic;
39
+ /**
40
+ * Validates the flags for programmatic mode
41
+ * @param {ExtractFlags} flags - The command flags to validate
42
+ * @throws {Error} If required flags are missing
43
+ * @returns {void}
44
+ */
19
45
  private validateProgrammaticFlags;
20
46
  }
@@ -1,24 +1,34 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  const tslib_1 = require("tslib");
4
- const sdk_1 = require("@directus/sdk");
5
4
  const core_1 = require("@oclif/core");
6
5
  const inquirer_1 = tslib_1.__importDefault(require("inquirer"));
7
6
  const node_fs_1 = tslib_1.__importDefault(require("node:fs"));
8
7
  const node_path_1 = tslib_1.__importDefault(require("node:path"));
9
8
  const slugify_1 = tslib_1.__importDefault(require("slugify"));
9
+ const customFlags = tslib_1.__importStar(require("../flags/common"));
10
10
  const constants_1 = require("../lib/constants");
11
11
  const extract_1 = tslib_1.__importDefault(require("../lib/extract/"));
12
- const sdk_2 = require("../lib/sdk");
13
12
  const auth_1 = require("../lib/utils/auth");
14
13
  const catch_error_1 = tslib_1.__importDefault(require("../lib/utils/catch-error"));
15
14
  const template_defaults_1 = require("../lib/utils/template-defaults");
16
15
  class ExtractCommand extends core_1.Command {
16
+ /**
17
+ * Main run method for the ExtractCommand
18
+ * @returns {Promise<void>} - Returns nothing
19
+ */
17
20
  async run() {
18
21
  const { flags } = await this.parse(ExtractCommand);
19
22
  const typedFlags = flags;
20
23
  await (typedFlags.programmatic ? this.runProgrammatic(typedFlags) : this.runInteractive(typedFlags));
21
24
  }
25
+ /**
26
+ * Extracts the template to the specified directory
27
+ * @param {string} templateName - The name of the template to extract
28
+ * @param {string} directory - The directory to extract the template to
29
+ * @param {ExtractFlags} flags - The command flags
30
+ * @returns {Promise<void>} - Returns nothing
31
+ */
22
32
  async extractTemplate(templateName, directory, flags) {
23
33
  try {
24
34
  if (!node_fs_1.default.existsSync(directory)) {
@@ -32,7 +42,11 @@ class ExtractCommand extends core_1.Command {
32
42
  node_fs_1.default.writeFileSync(readmePath, readmeContent);
33
43
  }
34
44
  catch (error) {
35
- core_1.ux.error(`Failed to create directory or write files: ${error.message}`);
45
+ (0, catch_error_1.default)(error, {
46
+ context: { function: 'extractTemplate' },
47
+ fatal: true,
48
+ logToFile: true,
49
+ });
36
50
  }
37
51
  core_1.ux.log(constants_1.SEPARATOR);
38
52
  core_1.ux.action.start(`Extracting template - ${core_1.ux.colorize(constants_1.DIRECTUS_PINK, templateName)} from ${core_1.ux.colorize(constants_1.DIRECTUS_PINK, flags.directusUrl)} to ${core_1.ux.colorize(constants_1.DIRECTUS_PINK, directory)}`);
@@ -42,24 +56,11 @@ class ExtractCommand extends core_1.Command {
42
56
  core_1.ux.log('Template extracted successfully.');
43
57
  this.exit(0);
44
58
  }
45
- async initializeDirectusApi(flags) {
46
- sdk_2.api.initialize(flags.directusUrl);
47
- try {
48
- if (flags.directusToken) {
49
- await sdk_2.api.loginWithToken(flags.directusToken);
50
- }
51
- else if (flags.userEmail && flags.userPassword) {
52
- await sdk_2.api.login(flags.userEmail, flags.userPassword);
53
- }
54
- const response = await sdk_2.api.client.request((0, sdk_1.readMe)());
55
- core_1.ux.log(`-- Logged in as ${response.first_name} ${response.last_name}`);
56
- }
57
- catch {
58
- (0, catch_error_1.default)('-- Unable to authenticate with the provided credentials. Please check your credentials.', {
59
- fatal: true,
60
- });
61
- }
62
- }
59
+ /**
60
+ * Runs the interactive mode for template extraction
61
+ * @param {ExtractFlags} flags - The command flags
62
+ * @returns {Promise<void>} - Returns nothing
63
+ */
63
64
  async runInteractive(flags) {
64
65
  core_1.ux.styledHeader(core_1.ux.colorize(constants_1.DIRECTUS_PURPLE, 'Directus Template CLI - Extract'));
65
66
  const templateName = await core_1.ux.prompt('What is the name of the template you would like to extract?');
@@ -91,23 +92,28 @@ class ExtractCommand extends core_1.Command {
91
92
  flags.userPassword = await core_1.ux.prompt('What is your password?', { type: 'hide' });
92
93
  }
93
94
  core_1.ux.log(constants_1.SEPARATOR);
94
- await this.initializeDirectusApi(flags);
95
+ await (0, auth_1.initializeDirectusApi)(flags);
95
96
  await this.extractTemplate(templateName, directory, flags);
96
97
  }
98
+ /**
99
+ * Runs the programmatic mode for template extraction
100
+ * @param {ExtractFlags} flags - The command flags
101
+ * @returns {Promise<void>} - Returns nothing
102
+ */
97
103
  async runProgrammatic(flags) {
98
104
  this.validateProgrammaticFlags(flags);
99
105
  const { templateLocation, templateName } = flags;
100
- await this.initializeDirectusApi(flags);
106
+ await (0, auth_1.initializeDirectusApi)(flags);
101
107
  await this.extractTemplate(templateName, templateLocation, flags);
102
108
  }
109
+ /**
110
+ * Validates the flags for programmatic mode
111
+ * @param {ExtractFlags} flags - The command flags to validate
112
+ * @throws {Error} If required flags are missing
113
+ * @returns {void}
114
+ */
103
115
  validateProgrammaticFlags(flags) {
104
- if (!flags.directusUrl) {
105
- core_1.ux.error('Directus URL is required for programmatic mode.');
106
- }
107
- // We need either a token or email/password
108
- if (!flags.directusToken && (!flags.userEmail || !flags.userPassword)) {
109
- core_1.ux.error('Either Directus token or email and password are required for programmatic mode.');
110
- }
116
+ (0, auth_1.validateAuthFlags)(flags);
111
117
  if (!flags.templateLocation) {
112
118
  core_1.ux.error('Template location is required for programmatic mode.');
113
119
  }
@@ -122,42 +128,12 @@ ExtractCommand.examples = [
122
128
  '$ directus-template-cli extract -p --templateName="My Template" --templateLocation="./my-template" --directusToken="admin-token-here" --directusUrl="http://localhost:8055"',
123
129
  ];
124
130
  ExtractCommand.flags = {
125
- directusToken: core_1.Flags.string({
126
- description: 'Token to use for the Directus instance',
127
- env: 'SOURCE_DIRECTUS_TOKEN',
128
- exclusive: ['userEmail', 'userPassword'],
129
- }),
130
- directusUrl: core_1.Flags.string({
131
- description: 'URL of the Directus instance to extract the template from',
132
- env: 'SOURCE_DIRECTUS_URL',
133
- }),
134
- programmatic: core_1.Flags.boolean({
135
- char: 'p',
136
- default: false,
137
- description: 'Run in programmatic mode (non-interactive) for use cases such as CI/CD pipelines.',
138
- summary: 'Run in programmatic mode',
139
- }),
140
- templateLocation: core_1.Flags.string({
141
- dependsOn: ['programmatic'],
142
- description: 'Directory to extract the template to',
143
- env: 'TEMPLATE_LOCATION',
144
- }),
145
- templateName: core_1.Flags.string({
146
- dependsOn: ['programmatic'],
147
- description: 'Name of the template',
148
- env: 'TEMPLATE_NAME',
149
- }),
150
- userEmail: core_1.Flags.string({
151
- dependsOn: ['userPassword'],
152
- description: 'Email for Directus authentication',
153
- env: 'SOURCE_DIRECTUS_EMAIL',
154
- exclusive: ['directusToken'],
155
- }),
156
- userPassword: core_1.Flags.string({
157
- dependsOn: ['userEmail'],
158
- description: 'Password for Directus authentication',
159
- env: 'SOURCE_DIRECTUS_PASSWORD',
160
- exclusive: ['directusToken'],
161
- }),
131
+ directusToken: customFlags.directusToken,
132
+ directusUrl: customFlags.directusUrl,
133
+ programmatic: customFlags.programmatic,
134
+ templateLocation: customFlags.templateLocation,
135
+ templateName: customFlags.templateName,
136
+ userEmail: customFlags.userEmail,
137
+ userPassword: customFlags.userPassword,
162
138
  };
163
139
  exports.default = ExtractCommand;
@@ -0,0 +1,7 @@
1
+ export declare const directusToken: import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces").CustomOptions>;
2
+ export declare const directusUrl: import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces").CustomOptions>;
3
+ export declare const userEmail: import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces").CustomOptions>;
4
+ export declare const userPassword: import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces").CustomOptions>;
5
+ export declare const programmatic: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
6
+ export declare const templateLocation: import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces").CustomOptions>;
7
+ export declare const templateName: import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces").CustomOptions>;
@@ -0,0 +1,41 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.templateName = exports.templateLocation = exports.programmatic = exports.userPassword = exports.userEmail = exports.directusUrl = exports.directusToken = void 0;
4
+ const core_1 = require("@oclif/core");
5
+ exports.directusToken = core_1.Flags.string({
6
+ description: 'Token to use for the Directus instance',
7
+ env: 'DIRECTUS_TOKEN',
8
+ exclusive: ['userEmail', 'userPassword'],
9
+ });
10
+ exports.directusUrl = core_1.Flags.string({
11
+ description: 'URL of the Directus instance',
12
+ env: 'DIRECTUS_URL',
13
+ });
14
+ exports.userEmail = core_1.Flags.string({
15
+ dependsOn: ['userPassword'],
16
+ description: 'Email for Directus authentication',
17
+ env: 'DIRECTUS_EMAIL',
18
+ exclusive: ['directusToken'],
19
+ });
20
+ exports.userPassword = core_1.Flags.string({
21
+ dependsOn: ['userEmail'],
22
+ description: 'Password for Directus authentication',
23
+ env: 'DIRECTUS_PASSWORD',
24
+ exclusive: ['directusToken'],
25
+ });
26
+ exports.programmatic = core_1.Flags.boolean({
27
+ char: 'p',
28
+ default: false,
29
+ description: 'Run in programmatic mode (non-interactive) for use cases such as CI/CD pipelines.',
30
+ summary: 'Run in programmatic mode',
31
+ });
32
+ exports.templateLocation = core_1.Flags.string({
33
+ dependsOn: ['programmatic'],
34
+ description: 'Location of the template',
35
+ env: 'TEMPLATE_LOCATION',
36
+ });
37
+ exports.templateName = core_1.Flags.string({
38
+ dependsOn: ['programmatic'],
39
+ description: 'Name of the template',
40
+ env: 'TEMPLATE_NAME',
41
+ });
@@ -0,0 +1,22 @@
1
+ export interface ApplyFlags {
2
+ content: boolean;
3
+ dashboards: boolean;
4
+ directusToken: string;
5
+ directusUrl: string;
6
+ extensions: boolean;
7
+ files: boolean;
8
+ flows: boolean;
9
+ partial: boolean;
10
+ permissions: boolean;
11
+ programmatic: boolean;
12
+ schema: boolean;
13
+ settings: boolean;
14
+ templateLocation: string;
15
+ templateType: 'community' | 'github' | 'local';
16
+ userEmail: string;
17
+ userPassword: string;
18
+ users: boolean;
19
+ }
20
+ export declare const loadFlags: readonly ["content", "dashboards", "extensions", "files", "flows", "permissions", "schema", "settings", "users"];
21
+ export declare function validateProgrammaticFlags(flags: ApplyFlags): ApplyFlags;
22
+ export declare function validateInteractiveFlags(flags: ApplyFlags): ApplyFlags;
@@ -0,0 +1,67 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.validateInteractiveFlags = exports.validateProgrammaticFlags = exports.loadFlags = void 0;
4
+ const tslib_1 = require("tslib");
5
+ const core_1 = require("@oclif/core");
6
+ const catch_error_1 = tslib_1.__importDefault(require("../utils/catch-error"));
7
+ exports.loadFlags = [
8
+ 'content',
9
+ 'dashboards',
10
+ 'extensions',
11
+ 'files',
12
+ 'flows',
13
+ 'permissions',
14
+ 'schema',
15
+ 'settings',
16
+ 'users',
17
+ ];
18
+ function validateProgrammaticFlags(flags) {
19
+ const { directusToken, directusUrl, templateLocation, userEmail, userPassword } = flags;
20
+ if (!directusUrl)
21
+ core_1.ux.error('Directus URL is required for programmatic mode.');
22
+ if (!directusToken && (!userEmail || !userPassword))
23
+ core_1.ux.error('Either Directus token or email and password are required for programmatic mode.');
24
+ if (!templateLocation)
25
+ core_1.ux.error('Template location is required for programmatic mode.');
26
+ return flags.partial ? handlePartialFlags(flags) : setAllFlagsTrue(flags);
27
+ }
28
+ exports.validateProgrammaticFlags = validateProgrammaticFlags;
29
+ function validateInteractiveFlags(flags) {
30
+ return flags.partial ? handlePartialFlags(flags) : setAllFlagsTrue(flags);
31
+ }
32
+ exports.validateInteractiveFlags = validateInteractiveFlags;
33
+ function handlePartialFlags(flags) {
34
+ const enabledFlags = exports.loadFlags.filter(flag => flags[flag] === true);
35
+ const disabledFlags = exports.loadFlags.filter(flag => flags[flag] === false);
36
+ if (enabledFlags.length > 0) {
37
+ for (const flag of exports.loadFlags)
38
+ flags[flag] = enabledFlags.includes(flag);
39
+ }
40
+ else if (disabledFlags.length > 0) {
41
+ for (const flag of exports.loadFlags)
42
+ flags[flag] = !disabledFlags.includes(flag);
43
+ }
44
+ else {
45
+ setAllFlagsTrue(flags);
46
+ }
47
+ handleDependencies(flags);
48
+ if (!exports.loadFlags.some(flag => flags[flag])) {
49
+ (0, catch_error_1.default)(new Error('When using --partial, at least one component must be loaded.'), { fatal: true });
50
+ }
51
+ return flags;
52
+ }
53
+ function handleDependencies(flags) {
54
+ if (flags.content && (!flags.schema || !flags.files)) {
55
+ flags.schema = flags.files = true;
56
+ core_1.ux.warn('Content loading requires schema and files. Enabling schema and files flags.');
57
+ }
58
+ if (flags.users && !flags.permissions) {
59
+ flags.permissions = true;
60
+ core_1.ux.warn('User loading requires permissions. Enabling permissions flag.');
61
+ }
62
+ }
63
+ function setAllFlagsTrue(flags) {
64
+ for (const flag of exports.loadFlags)
65
+ flags[flag] = true;
66
+ return flags;
67
+ }
package/dist/lib/sdk.d.ts CHANGED
@@ -1,6 +1,19 @@
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>;
16
+ }
4
17
  declare class Api {
5
18
  client: (RestClient<Schema> & AuthenticationClient<Schema>) | undefined;
6
19
  private authData;
@@ -12,6 +25,7 @@ declare class Api {
12
25
  loginWithToken(token: string): Promise<void>;
13
26
  logout(): Promise<void>;
14
27
  refreshToken(): Promise<void>;
28
+ private enhancedFetch;
15
29
  }
16
30
  declare const api: Api;
17
31
  export { api };
package/dist/lib/sdk.js CHANGED
@@ -1,17 +1,89 @@
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
6
  const bottleneck_1 = tslib_1.__importDefault(require("bottleneck"));
7
+ class DirectusError extends Error {
8
+ constructor(response) {
9
+ super(response.statusText);
10
+ this.name = 'DirectusError';
11
+ this.headers = response.headers;
12
+ this.status = response.status;
13
+ this.response = response;
14
+ this.errors = [];
15
+ this.message = response.statusText;
16
+ }
17
+ formatError() {
18
+ if (this.errors.length === 0) {
19
+ return `Directus Error: ${this.message} (Status: ${this.status})`;
20
+ }
21
+ const { extensions, message } = this.errors[0];
22
+ let formattedError = `Directus Error: ${message.trim()} (Status: ${this.status})`;
23
+ if (extensions) {
24
+ formattedError += ` ${JSON.stringify(extensions)}`;
25
+ }
26
+ return formattedError;
27
+ }
28
+ async parseErrors() {
29
+ try {
30
+ const data = await this.response.json();
31
+ if (data && Array.isArray(data.errors)) {
32
+ this.errors = data.errors;
33
+ this.message = this.formatError();
34
+ }
35
+ }
36
+ catch {
37
+ // If parsing fails, keep the errors array empty
38
+ }
39
+ }
40
+ }
41
+ exports.DirectusError = DirectusError;
7
42
  class Api {
8
43
  constructor() {
9
44
  this.authData = null;
10
45
  this.limiter = new bottleneck_1.default({
11
46
  maxConcurrent: 10,
12
- minTime: 100,
13
- retryCount: 3,
14
- retryDelay: 3000,
47
+ minTime: 100, // Ensure at least 100ms between requests
48
+ reservoir: 50, // Reservoir to handle the default rate limiter of 50 requests per second
49
+ reservoirRefreshAmount: 50,
50
+ reservoirRefreshInterval: 1000, // Refill 50 requests every 1 second
51
+ retryCount: 3, // Retry a maximum of 3 times
52
+ });
53
+ this.limiter.on('failed', async (error, jobInfo) => {
54
+ var _a;
55
+ if (error instanceof DirectusError) {
56
+ const retryAfter = (_a = error.headers) === null || _a === void 0 ? void 0 : _a.get('Retry-After');
57
+ const statusCode = error.status;
58
+ if (statusCode === 429) {
59
+ const delay = retryAfter ? Number.parseInt(retryAfter, 10) * 1000 : 60000;
60
+ console.log(`-- Rate limited. Retrying after ${delay}ms`);
61
+ return delay;
62
+ }
63
+ if (statusCode === 503) {
64
+ const delay = retryAfter ? Number.parseInt(retryAfter, 10) * 1000 : 5000;
65
+ console.log(`-- Server under pressure. Retrying after ${delay}ms`);
66
+ return delay;
67
+ }
68
+ if (statusCode === 400) {
69
+ return;
70
+ }
71
+ }
72
+ // For other errors, use exponential backoff, but only if we haven't exceeded retryCount
73
+ if (jobInfo.retryCount < 3) {
74
+ const delay = Math.min(1000 * 2 ** jobInfo.retryCount, 30000);
75
+ console.log(`Request failed. Retrying after ${delay}ms`);
76
+ return delay;
77
+ }
78
+ console.log('Max retries reached, not retrying further');
79
+ });
80
+ this.limiter.on('retry', (error, jobInfo) => {
81
+ console.log(`Retrying job (attempt ${jobInfo.retryCount + 1})`);
82
+ });
83
+ this.limiter.on('depleted', empty => {
84
+ if (empty) {
85
+ console.log('Rate limit quota depleted. Requests will be queued.');
86
+ }
15
87
  });
16
88
  }
17
89
  getToken() {
@@ -21,7 +93,7 @@ class Api {
21
93
  initialize(url) {
22
94
  this.client = (0, sdk_1.createDirectus)(url, {
23
95
  globals: {
24
- fetch: (...args) => this.limiter.schedule(() => fetch(...args)),
96
+ fetch: this.limiter.wrap(this.enhancedFetch),
25
97
  },
26
98
  })
27
99
  .with((0, sdk_1.rest)())
@@ -60,6 +132,15 @@ class Api {
60
132
  }
61
133
  await this.client.refresh();
62
134
  }
135
+ async enhancedFetch(...args) {
136
+ const response = await fetch(...args);
137
+ if (!response.ok) {
138
+ const error = new DirectusError(response);
139
+ await error.parseErrors();
140
+ throw error;
141
+ }
142
+ return response;
143
+ }
63
144
  }
64
145
  const api = new Api();
65
146
  exports.api = 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,9 +22,14 @@ 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
34
  await sdk_2.api.loginWithToken(directusToken);
26
35
  const response = await sdk_2.api.client.request((0, sdk_1.readMe)());
@@ -38,3 +47,39 @@ async function getDirectusToken(directusUrl) {
38
47
  }
39
48
  }
40
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
- : `${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
26
  errorMessage,
15
- contextString && `Context: ${contextString}`,
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
- }
@@ -24,7 +24,7 @@
24
24
  },
25
25
  "directusToken": {
26
26
  "description": "Token to use for the Directus instance",
27
- "env": "TARGET_DIRECTUS_TOKEN",
27
+ "env": "DIRECTUS_TOKEN",
28
28
  "exclusive": [
29
29
  "userEmail",
30
30
  "userPassword"
@@ -35,8 +35,8 @@
35
35
  "type": "option"
36
36
  },
37
37
  "directusUrl": {
38
- "description": "URL of the Directus instance to apply the template to",
39
- "env": "TARGET_DIRECTUS_URL",
38
+ "description": "URL of the Directus instance",
39
+ "env": "DIRECTUS_URL",
40
40
  "name": "directusUrl",
41
41
  "hasDynamicHelp": false,
42
42
  "multiple": false,
@@ -99,10 +99,9 @@
99
99
  },
100
100
  "templateLocation": {
101
101
  "dependsOn": [
102
- "programmatic",
103
- "templateType"
102
+ "programmatic"
104
103
  ],
105
- "description": "Location of the template to apply",
104
+ "description": "Location of the template",
106
105
  "env": "TEMPLATE_LOCATION",
107
106
  "name": "templateLocation",
108
107
  "hasDynamicHelp": false,
@@ -132,7 +131,7 @@
132
131
  "userPassword"
133
132
  ],
134
133
  "description": "Email for Directus authentication",
135
- "env": "TARGET_DIRECTUS_EMAIL",
134
+ "env": "DIRECTUS_EMAIL",
136
135
  "exclusive": [
137
136
  "directusToken"
138
137
  ],
@@ -146,7 +145,7 @@
146
145
  "userEmail"
147
146
  ],
148
147
  "description": "Password for Directus authentication",
149
- "env": "TARGET_DIRECTUS_PASSWORD",
148
+ "env": "DIRECTUS_PASSWORD",
150
149
  "exclusive": [
151
150
  "directusToken"
152
151
  ],
@@ -188,7 +187,7 @@
188
187
  "flags": {
189
188
  "directusToken": {
190
189
  "description": "Token to use for the Directus instance",
191
- "env": "SOURCE_DIRECTUS_TOKEN",
190
+ "env": "DIRECTUS_TOKEN",
192
191
  "exclusive": [
193
192
  "userEmail",
194
193
  "userPassword"
@@ -199,8 +198,8 @@
199
198
  "type": "option"
200
199
  },
201
200
  "directusUrl": {
202
- "description": "URL of the Directus instance to extract the template from",
203
- "env": "SOURCE_DIRECTUS_URL",
201
+ "description": "URL of the Directus instance",
202
+ "env": "DIRECTUS_URL",
204
203
  "name": "directusUrl",
205
204
  "hasDynamicHelp": false,
206
205
  "multiple": false,
@@ -218,7 +217,7 @@
218
217
  "dependsOn": [
219
218
  "programmatic"
220
219
  ],
221
- "description": "Directory to extract the template to",
220
+ "description": "Location of the template",
222
221
  "env": "TEMPLATE_LOCATION",
223
222
  "name": "templateLocation",
224
223
  "hasDynamicHelp": false,
@@ -241,7 +240,7 @@
241
240
  "userPassword"
242
241
  ],
243
242
  "description": "Email for Directus authentication",
244
- "env": "SOURCE_DIRECTUS_EMAIL",
243
+ "env": "DIRECTUS_EMAIL",
245
244
  "exclusive": [
246
245
  "directusToken"
247
246
  ],
@@ -255,7 +254,7 @@
255
254
  "userEmail"
256
255
  ],
257
256
  "description": "Password for Directus authentication",
258
- "env": "SOURCE_DIRECTUS_PASSWORD",
257
+ "env": "DIRECTUS_PASSWORD",
259
258
  "exclusive": [
260
259
  "directusToken"
261
260
  ],
@@ -281,5 +280,5 @@
281
280
  ]
282
281
  }
283
282
  },
284
- "version": "0.5.0-beta.12"
283
+ "version": "0.5.0-beta.13"
285
284
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "directus-template-cli",
3
- "version": "0.5.0-beta.12",
3
+ "version": "0.5.0-beta.13",
4
4
  "description": "CLI Utility for applying templates to a Directus instance.",
5
5
  "author": "bryantgillespie @bryantgillespie",
6
6
  "bin": {
@@ -1,8 +0,0 @@
1
- export interface Diff {
2
- diff: {
3
- collections: any[];
4
- fields: any[];
5
- relations: any[];
6
- };
7
- hash: string;
8
- }
@@ -1,2 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });