directus-template-cli 0.5.0-beta.11 → 0.5.0-beta.12
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 +139 -12
- package/dist/commands/apply.d.ts +8 -0
- package/dist/commands/apply.js +122 -59
- package/dist/commands/extract.d.ts +2 -0
- package/dist/commands/extract.js +56 -10
- 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 +18 -20
- 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/index.js +5 -5
- package/dist/lib/load/load-collections.js +1 -0
- package/dist/lib/load/load-data.js +3 -3
- package/dist/lib/sdk.d.ts +6 -2
- package/dist/lib/sdk.js +39 -7
- package/dist/lib/utils/auth.js +1 -2
- package/oclif.manifest.json +65 -18
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -4,21 +4,20 @@ A CLI tool to make applying or extracting Directus "templates" a little easier..
|
|
|
4
4
|
|
|
5
5
|
**Notes:**
|
|
6
6
|
|
|
7
|
-
- This is a
|
|
8
|
-
- ⚠️ Known issues with using MySQL currently, please use ONLY PostgreSQL or SQLite for your database provider.
|
|
9
|
-
- Templates are applied / extracted on an all or nothing basis – meaning that all the schema, content, and system settings are extracted or applied. We'd love to support more granular operations in the future. (PRs welcome 🙏)
|
|
10
|
-
- 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.
|
|
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.
|
|
11
8
|
|
|
12
|
-
|
|
9
|
+
- ⚠️ 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
|
+
- 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.
|
|
13
11
|
|
|
14
|
-
|
|
15
|
-
- When applying templates, the schema snapshot / schema diff is no longer used to create collections, fields, and relations. This allows support for loading multiple templates into a single instance.
|
|
12
|
+
Breaking Changes in v0.5.0
|
|
16
13
|
|
|
17
|
-
|
|
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`.
|
|
18
15
|
|
|
19
16
|
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.
|
|
20
17
|
|
|
21
|
-
|
|
18
|
+
## Applying a Template
|
|
19
|
+
|
|
20
|
+
🚧 Make backups of your project/database before applying templates.
|
|
22
21
|
|
|
23
22
|
1. Create a Directus instance on [Directus Cloud](https://directus.cloud) or using self-hosted version.
|
|
24
23
|
2. Login and create a Static Access Token for the admin user.
|
|
@@ -29,20 +28,148 @@ Using the @latest tag ensures you're receiving the latest version of the package
|
|
|
29
28
|
$ npx directus-template-cli@latest apply
|
|
30
29
|
```
|
|
31
30
|
|
|
32
|
-
You can choose from our
|
|
31
|
+
You can choose from our community maintained templates or you can also choose a template from a local directory or a public GitHub repository.
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
### Programmatic Mode
|
|
35
|
+
|
|
36
|
+
For CI/CD pipelines or automated scripts, you can use the programmatic mode:
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
Using a token:
|
|
40
|
+
|
|
41
|
+
```
|
|
42
|
+
$ npx directus-template-cli@latest apply -p --directusUrl="http://localhost:8055" --directusToken="admin-token-here" --templateLocation="./my-template" --templateType="local"
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
Using email/password:
|
|
46
|
+
|
|
47
|
+
```
|
|
48
|
+
npx directus-template-cli@latest apply -p --directusUrl="http://localhost:8055" --userEmail="admin@example.com" --userPassword="admin" --templateLocation="./my-template" --templateType="local"
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
Partial apply (apply only some of the parts of a template to the instance):
|
|
52
|
+
|
|
53
|
+
```
|
|
54
|
+
npx directus-template-cli@latest apply -p --directusUrl="http://localhost:8055" --userEmail="admin@example.com" --userPassword="your-password" --templateLocation="./my-template" --templateType="local" --partial --schema --permissions --no-content
|
|
55
|
+
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
Available flags for programmatic mode:
|
|
59
|
+
|
|
60
|
+
- `--directusUrl`: URL of the Directus instance to apply the template to (required)
|
|
61
|
+
- `--directusToken`: Token to use for the Directus instance (required if not using email/password)
|
|
62
|
+
- `--userEmail`: Email for Directus authentication (required if not using token)
|
|
63
|
+
- `--userPassword`: Password for Directus authentication (required if not using token)
|
|
64
|
+
- `--templateLocation`: Location of the template to apply (required)
|
|
65
|
+
- `--templateType`: Type of template to apply. Options: community, local, github (required)
|
|
66
|
+
- `--partial`: Enable partial template application
|
|
67
|
+
- `--content`: Load Content (data)
|
|
68
|
+
- `--dashboards`: Load Dashboards
|
|
69
|
+
- `--extensions`: Load Extensions
|
|
70
|
+
- `--files`: Load Files
|
|
71
|
+
- `--flows`: Load Flows
|
|
72
|
+
- `--permissions`: Load Permissions
|
|
73
|
+
- `--schema`: Load Schema
|
|
74
|
+
- `--settings`: Load Settings
|
|
75
|
+
- `--users`: Load Users
|
|
76
|
+
|
|
77
|
+
When using `--partial`, you can also use `--no` flags to exclude specific components from being applied. For example:
|
|
78
|
+
|
|
79
|
+
```
|
|
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
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
This command will apply the template but exclude content and users. Available `--no` flags include:
|
|
84
|
+
|
|
85
|
+
- `--no-content`: Skip loading Content (data)
|
|
86
|
+
- `--no-dashboards`: Skip loading Dashboards
|
|
87
|
+
- `--no-extensions`: Skip loading Extensions
|
|
88
|
+
- `--no-files`: Skip loading Files
|
|
89
|
+
- `--no-flows`: Skip loading Flows
|
|
90
|
+
- `--no-permissions`: Skip loading PermissionsI
|
|
91
|
+
- `--no-schema`: Skip loading Schema
|
|
92
|
+
- `--no-settings`: Skip loading Settings
|
|
93
|
+
- `--no-users`: Skip loading Users
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
#### Template Component Dependencies
|
|
33
97
|
|
|
34
|
-
|
|
98
|
+
When applying templates, certain components have dependencies on others. Here are the key relationships to be aware of:
|
|
99
|
+
|
|
100
|
+
- `--users`: Depends on `--permissions`. If you include users, permissions will automatically be included.
|
|
101
|
+
- `--permissions`: Depends on `--schema`. If you include permissions, the schema will automatically be included.
|
|
102
|
+
- `--content`: Depends on `--schema`. If you include content, the schema will automatically be included.
|
|
103
|
+
- `--files`: No direct dependencies, but often related to content. Consider including `--content` if you're including files.
|
|
104
|
+
- `--flows`: No direct dependencies, but may interact with other components. Consider your specific use case.
|
|
105
|
+
- `--dashboards`: No direct dependencies, but often rely on data from other components.
|
|
106
|
+
- `--extensions`: No direct dependencies, but may interact with other components.
|
|
107
|
+
- `--settings`: No direct dependencies, but affects the overall system configuration.
|
|
108
|
+
|
|
109
|
+
When using the `--partial` flag, keep these dependencies in mind. For example:
|
|
110
|
+
|
|
111
|
+
```
|
|
112
|
+
$ npx directus-template-cli@latest apply -p --directusUrl="http://localhost:8055" --directusToken="admin-token-here" --templateLocation="./my-template" --templateType="local" --partial --users
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
This command will automatically include `--permissions` and `--schema` along with `--users`, even if not explicitly specified.
|
|
116
|
+
|
|
117
|
+
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
|
+
|
|
119
|
+
## Extracting a Template
|
|
35
120
|
|
|
36
121
|
The CLI can also extract a template from a Directus instance so that it can be applied to other instances.
|
|
37
122
|
|
|
123
|
+
Note: We do not currently support partial extraction. The entire template will be extracted. We thought it better to have the data and not need it, than need it and not have it.
|
|
124
|
+
|
|
38
125
|
1. Make sure you remove any sensitive data from the Directus instance you don't want to include in the template.
|
|
39
126
|
2. Login and create a Static Access Token for the admin user.
|
|
40
127
|
3. Copy the static token and your Directus URL.
|
|
41
128
|
4. Run the following command on the terminal and follow the prompts.
|
|
42
129
|
|
|
43
130
|
```
|
|
44
|
-
|
|
131
|
+
npx directus-template-cli@latest extract
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
### Programmatic Mode
|
|
135
|
+
|
|
136
|
+
For CI/CD pipelines or automated scripts, you can use the programmatic mode:
|
|
137
|
+
|
|
138
|
+
Using a token:
|
|
139
|
+
|
|
45
140
|
```
|
|
141
|
+
npx directus-template-cli@latest extract -p --templateName="My Template" --templateLocation="./my-template" --directusToken="admin-token-here" --directusUrl="http://localhost:8055"
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
Using email/password:
|
|
145
|
+
|
|
146
|
+
```
|
|
147
|
+
npx directus-template-cli@latest extract -p --templateName="My Template" --templateLocation="./my-template" --userEmail="admin@example.com" --userPassword="admin" --directusUrl="http://localhost:8055"
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
Available flags for programmatic mode:
|
|
151
|
+
|
|
152
|
+
- `--directusUrl`: URL of the Directus instance to extract the template from (required)
|
|
153
|
+
- `--directusToken`: Token to use for the Directus instance (required if not using email/password)
|
|
154
|
+
- `--userEmail`: Email for Directus authentication (required if not using token)
|
|
155
|
+
- `--userPassword`: Password for Directus authentication (required if not using token)
|
|
156
|
+
- `--templateLocation`: Directory to extract the template to (required)
|
|
157
|
+
- `--templateName`: Name of the template (required)
|
|
158
|
+
|
|
159
|
+
## Logs
|
|
160
|
+
|
|
161
|
+
The Directus Template CLI logs information to a file in the `.directus-template-cli/logs` directory.
|
|
162
|
+
|
|
163
|
+
### Log Generation
|
|
164
|
+
|
|
165
|
+
Logs are automatically generated for each run of the CLI. Here's how the logging system works:
|
|
166
|
+
- A new log file is created for each CLI run.
|
|
167
|
+
- Log files are stored in the `.directus-template-cli/logs` directory within your current working directory.
|
|
168
|
+
- Each log file is named `run-[timestamp].log`, where `[timestamp]` is the ISO timestamp of when the CLI was initiated.
|
|
169
|
+
|
|
170
|
+
The logger automatically sanitizes sensitive information such as passwords, tokens, and keys before writing to the log file. But it may not catch everything. Just be aware of this and make sure to remove the log files when they are no longer needed.
|
|
171
|
+
|
|
172
|
+
Note: If you encounter any issues with the CLI, providing these log files can greatly assist in diagnosing and resolving the problem.
|
|
46
173
|
|
|
47
174
|
## License
|
|
48
175
|
|
package/dist/commands/apply.d.ts
CHANGED
|
@@ -17,13 +17,21 @@ export default class ApplyCommand extends Command {
|
|
|
17
17
|
settings: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
|
|
18
18
|
templateLocation: import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
19
19
|
templateType: import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
20
|
+
userEmail: import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
21
|
+
userPassword: import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
20
22
|
users: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
|
|
21
23
|
};
|
|
22
24
|
run(): Promise<void>;
|
|
25
|
+
private checkAtLeastOneFlagEnabled;
|
|
26
|
+
private handleDependencies;
|
|
27
|
+
private handlePartialFlags;
|
|
23
28
|
private initializeDirectusApi;
|
|
24
29
|
private redirectToDirectusPlus;
|
|
25
30
|
private runInteractive;
|
|
26
31
|
private runProgrammatic;
|
|
27
32
|
private selectLocalTemplate;
|
|
33
|
+
private setAllFlagsTrue;
|
|
34
|
+
private setSpecificFlags;
|
|
28
35
|
private validateFlags;
|
|
36
|
+
private validateProgrammaticFlags;
|
|
29
37
|
}
|
package/dist/commands/apply.js
CHANGED
|
@@ -19,15 +19,56 @@ class ApplyCommand extends core_1.Command {
|
|
|
19
19
|
const typedFlags = flags;
|
|
20
20
|
await (typedFlags.programmatic ? this.runProgrammatic(typedFlags) : this.runInteractive(typedFlags));
|
|
21
21
|
}
|
|
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
|
+
}
|
|
22
58
|
async initializeDirectusApi(flags) {
|
|
23
59
|
sdk_2.api.initialize(flags.directusUrl);
|
|
24
60
|
try {
|
|
25
|
-
|
|
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
|
+
}
|
|
26
67
|
const response = await sdk_2.api.client.request((0, sdk_1.readMe)());
|
|
27
68
|
core_1.ux.log(`-- Logged in as ${response.first_name} ${response.last_name}`);
|
|
28
69
|
}
|
|
29
70
|
catch {
|
|
30
|
-
(0, catch_error_1.default)('--
|
|
71
|
+
(0, catch_error_1.default)('-- Unable to authenticate with the provided credentials. Please check your credentials.', {
|
|
31
72
|
fatal: true,
|
|
32
73
|
});
|
|
33
74
|
}
|
|
@@ -39,7 +80,7 @@ class ApplyCommand extends core_1.Command {
|
|
|
39
80
|
}
|
|
40
81
|
async runInteractive(flags) {
|
|
41
82
|
const validatedFlags = this.validateFlags(flags);
|
|
42
|
-
core_1.ux.styledHeader(core_1.ux.colorize(constants_1.DIRECTUS_PURPLE, '
|
|
83
|
+
core_1.ux.styledHeader(core_1.ux.colorize(constants_1.DIRECTUS_PURPLE, 'Directus Template CLI - Apply'));
|
|
43
84
|
const templateType = await inquirer.prompt([
|
|
44
85
|
{
|
|
45
86
|
choices: [
|
|
@@ -84,11 +125,33 @@ class ApplyCommand extends core_1.Command {
|
|
|
84
125
|
}
|
|
85
126
|
core_1.ux.log(`You selected ${core_1.ux.colorize(constants_1.DIRECTUS_PINK, template.templateName)}`);
|
|
86
127
|
core_1.ux.log(constants_1.SEPARATOR);
|
|
87
|
-
// Get Directus URL
|
|
128
|
+
// Get Directus URL
|
|
88
129
|
const directusUrl = await (0, auth_1.getDirectusUrl)();
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
130
|
+
validatedFlags.directusUrl = directusUrl;
|
|
131
|
+
// Prompt for login method
|
|
132
|
+
const loginMethod = await inquirer.prompt([
|
|
133
|
+
{
|
|
134
|
+
choices: [
|
|
135
|
+
{ name: 'Directus Access Token', value: 'token' },
|
|
136
|
+
{ name: 'Email and Password', value: 'email' },
|
|
137
|
+
],
|
|
138
|
+
default: 'token',
|
|
139
|
+
message: 'How do you want to log in?',
|
|
140
|
+
name: 'loginMethod',
|
|
141
|
+
type: 'list',
|
|
142
|
+
},
|
|
143
|
+
]);
|
|
144
|
+
if (loginMethod.loginMethod === 'token') {
|
|
145
|
+
const directusToken = await (0, auth_1.getDirectusToken)(directusUrl);
|
|
146
|
+
validatedFlags.directusToken = directusToken;
|
|
147
|
+
}
|
|
148
|
+
else {
|
|
149
|
+
const userEmail = await core_1.ux.prompt('What is your email?');
|
|
150
|
+
validatedFlags.userEmail = userEmail;
|
|
151
|
+
const userPassword = await core_1.ux.prompt('What is your password?');
|
|
152
|
+
validatedFlags.userPassword = userPassword;
|
|
153
|
+
}
|
|
154
|
+
await this.initializeDirectusApi(validatedFlags);
|
|
92
155
|
if (template) {
|
|
93
156
|
core_1.ux.styledHeader(core_1.ux.colorize(constants_1.DIRECTUS_PURPLE, `Applying template - ${template.templateName} to ${directusUrl}`));
|
|
94
157
|
await (0, index_js_1.default)(template.directoryPath, validatedFlags);
|
|
@@ -156,15 +219,19 @@ class ApplyCommand extends core_1.Command {
|
|
|
156
219
|
}
|
|
157
220
|
}
|
|
158
221
|
}
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
core_1.ux.error('Directus URL and token are required for programmatic mode.');
|
|
163
|
-
}
|
|
164
|
-
if (!flags.templateLocation) {
|
|
165
|
-
core_1.ux.error('Template location is required for programmatic mode.');
|
|
166
|
-
}
|
|
222
|
+
setAllFlagsTrue(flags, loadFlags) {
|
|
223
|
+
for (const flag of loadFlags) {
|
|
224
|
+
flags[flag] = true;
|
|
167
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);
|
|
168
235
|
const loadFlags = [
|
|
169
236
|
'content',
|
|
170
237
|
'dashboards',
|
|
@@ -177,34 +244,30 @@ class ApplyCommand extends core_1.Command {
|
|
|
177
244
|
'users',
|
|
178
245
|
];
|
|
179
246
|
const validatedFlags = { ...flags };
|
|
247
|
+
if (flags.programmatic && !flags.partial) {
|
|
248
|
+
return this.setAllFlagsTrue(validatedFlags, loadFlags);
|
|
249
|
+
}
|
|
180
250
|
if (flags.partial) {
|
|
181
|
-
|
|
182
|
-
if (enabledFlags.length === 0) {
|
|
183
|
-
core_1.ux.error('When using --partial, at least one component flag must be set to true.');
|
|
184
|
-
}
|
|
185
|
-
// Handle dependencies
|
|
186
|
-
if (flags.content) {
|
|
187
|
-
validatedFlags.schema = true;
|
|
188
|
-
validatedFlags.files = true;
|
|
189
|
-
if (!flags.schema || !flags.files) {
|
|
190
|
-
core_1.ux.warn('Content loading requires schema and files. Enabling schema and files flags.');
|
|
191
|
-
}
|
|
192
|
-
}
|
|
193
|
-
if (flags.users) {
|
|
194
|
-
validatedFlags.permissions = true;
|
|
195
|
-
if (!flags.permissions) {
|
|
196
|
-
core_1.ux.warn('User loading requires permissions. Enabling permissions flag.');
|
|
197
|
-
}
|
|
198
|
-
}
|
|
251
|
+
this.handlePartialFlags(validatedFlags, flags, loadFlags);
|
|
199
252
|
}
|
|
200
253
|
else {
|
|
201
|
-
|
|
202
|
-
for (const flag of loadFlags) {
|
|
203
|
-
validatedFlags[flag] = true;
|
|
204
|
-
}
|
|
254
|
+
this.setAllFlagsTrue(validatedFlags, loadFlags);
|
|
205
255
|
}
|
|
206
256
|
return validatedFlags;
|
|
207
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
|
+
}
|
|
208
271
|
}
|
|
209
272
|
ApplyCommand.description = 'Apply a template to a blank Directus instance.';
|
|
210
273
|
ApplyCommand.examples = [
|
|
@@ -215,20 +278,18 @@ ApplyCommand.examples = [
|
|
|
215
278
|
ApplyCommand.flags = {
|
|
216
279
|
content: core_1.Flags.boolean({
|
|
217
280
|
allowNo: true,
|
|
218
|
-
default:
|
|
281
|
+
default: undefined,
|
|
219
282
|
description: 'Load Content (data)',
|
|
220
|
-
relationships: [
|
|
221
|
-
{ flags: ['schema', 'files'], type: 'all' },
|
|
222
|
-
],
|
|
223
283
|
}),
|
|
224
284
|
dashboards: core_1.Flags.boolean({
|
|
225
285
|
allowNo: true,
|
|
226
|
-
default:
|
|
286
|
+
default: undefined,
|
|
227
287
|
description: 'Load Dashboards (dashboards, panels)',
|
|
228
288
|
}),
|
|
229
289
|
directusToken: core_1.Flags.string({
|
|
230
290
|
description: 'Token to use for the Directus instance',
|
|
231
291
|
env: 'TARGET_DIRECTUS_TOKEN',
|
|
292
|
+
exclusive: ['userEmail', 'userPassword'],
|
|
232
293
|
}),
|
|
233
294
|
directusUrl: core_1.Flags.string({
|
|
234
295
|
description: 'URL of the Directus instance to apply the template to',
|
|
@@ -236,17 +297,17 @@ ApplyCommand.flags = {
|
|
|
236
297
|
}),
|
|
237
298
|
extensions: core_1.Flags.boolean({
|
|
238
299
|
allowNo: true,
|
|
239
|
-
default:
|
|
300
|
+
default: undefined,
|
|
240
301
|
description: 'Load Extensions',
|
|
241
302
|
}),
|
|
242
303
|
files: core_1.Flags.boolean({
|
|
243
304
|
allowNo: true,
|
|
244
|
-
default:
|
|
305
|
+
default: undefined,
|
|
245
306
|
description: 'Load Files (files, folders)',
|
|
246
307
|
}),
|
|
247
308
|
flows: core_1.Flags.boolean({
|
|
248
309
|
allowNo: true,
|
|
249
|
-
default:
|
|
310
|
+
default: undefined,
|
|
250
311
|
description: 'Load Flows (operations, flows)',
|
|
251
312
|
}),
|
|
252
313
|
partial: core_1.Flags.boolean({
|
|
@@ -256,7 +317,7 @@ ApplyCommand.flags = {
|
|
|
256
317
|
}),
|
|
257
318
|
permissions: core_1.Flags.boolean({
|
|
258
319
|
allowNo: true,
|
|
259
|
-
default:
|
|
320
|
+
default: undefined,
|
|
260
321
|
description: 'Loads permissions data. Collections include: directus_roles, directus_policies, directus_access, directus_permissions.',
|
|
261
322
|
summary: 'Load permissions (roles, policies, access, permissions)',
|
|
262
323
|
}),
|
|
@@ -268,12 +329,12 @@ ApplyCommand.flags = {
|
|
|
268
329
|
}),
|
|
269
330
|
schema: core_1.Flags.boolean({
|
|
270
331
|
allowNo: true,
|
|
271
|
-
default:
|
|
332
|
+
default: undefined,
|
|
272
333
|
description: 'Load schema (collections, relations)',
|
|
273
334
|
}),
|
|
274
335
|
settings: core_1.Flags.boolean({
|
|
275
336
|
allowNo: true,
|
|
276
|
-
default:
|
|
337
|
+
default: undefined,
|
|
277
338
|
description: 'Load settings (project settings, translations, presets)',
|
|
278
339
|
}),
|
|
279
340
|
templateLocation: core_1.Flags.string({
|
|
@@ -289,20 +350,22 @@ ApplyCommand.flags = {
|
|
|
289
350
|
options: ['community', 'local', 'github'],
|
|
290
351
|
summary: 'Type of template to apply. Options: community, local, github.',
|
|
291
352
|
}),
|
|
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
|
+
}),
|
|
292
365
|
users: core_1.Flags.boolean({
|
|
293
366
|
allowNo: true,
|
|
294
|
-
default:
|
|
367
|
+
default: undefined,
|
|
295
368
|
description: 'Load users',
|
|
296
|
-
relationships: [
|
|
297
|
-
{ flags: ['permissions'], type: 'all' },
|
|
298
|
-
],
|
|
299
369
|
}),
|
|
300
370
|
};
|
|
301
371
|
exports.default = ApplyCommand;
|
|
302
|
-
function templateFlagsDefault({ flags }) {
|
|
303
|
-
// If programmatic is true, and partial is not set, return true
|
|
304
|
-
if (flags.programmatic && !flags.partial) {
|
|
305
|
-
return true;
|
|
306
|
-
}
|
|
307
|
-
return false;
|
|
308
|
-
}
|
|
@@ -8,6 +8,8 @@ export default class ExtractCommand extends Command {
|
|
|
8
8
|
programmatic: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
|
|
9
9
|
templateLocation: import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
10
10
|
templateName: import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
11
|
+
userEmail: import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
12
|
+
userPassword: import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
11
13
|
};
|
|
12
14
|
run(): Promise<void>;
|
|
13
15
|
private extractTemplate;
|
package/dist/commands/extract.js
CHANGED
|
@@ -3,6 +3,7 @@ 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 inquirer_1 = tslib_1.__importDefault(require("inquirer"));
|
|
6
7
|
const node_fs_1 = tslib_1.__importDefault(require("node:fs"));
|
|
7
8
|
const node_path_1 = tslib_1.__importDefault(require("node:path"));
|
|
8
9
|
const slugify_1 = tslib_1.__importDefault(require("slugify"));
|
|
@@ -34,7 +35,7 @@ class ExtractCommand extends core_1.Command {
|
|
|
34
35
|
core_1.ux.error(`Failed to create directory or write files: ${error.message}`);
|
|
35
36
|
}
|
|
36
37
|
core_1.ux.log(constants_1.SEPARATOR);
|
|
37
|
-
core_1.ux.action.start(`Extracting template - from ${flags.directusUrl} to ${directory}`);
|
|
38
|
+
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)}`);
|
|
38
39
|
await (0, extract_1.default)(directory);
|
|
39
40
|
core_1.ux.action.stop();
|
|
40
41
|
core_1.ux.log(constants_1.SEPARATOR);
|
|
@@ -44,25 +45,53 @@ class ExtractCommand extends core_1.Command {
|
|
|
44
45
|
async initializeDirectusApi(flags) {
|
|
45
46
|
sdk_2.api.initialize(flags.directusUrl);
|
|
46
47
|
try {
|
|
47
|
-
|
|
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
|
+
}
|
|
48
54
|
const response = await sdk_2.api.client.request((0, sdk_1.readMe)());
|
|
49
55
|
core_1.ux.log(`-- Logged in as ${response.first_name} ${response.last_name}`);
|
|
50
56
|
}
|
|
51
57
|
catch {
|
|
52
|
-
(0, catch_error_1.default)('
|
|
58
|
+
(0, catch_error_1.default)('-- Unable to authenticate with the provided credentials. Please check your credentials.', {
|
|
53
59
|
fatal: true,
|
|
54
60
|
});
|
|
55
61
|
}
|
|
56
62
|
}
|
|
57
63
|
async runInteractive(flags) {
|
|
58
|
-
|
|
64
|
+
core_1.ux.styledHeader(core_1.ux.colorize(constants_1.DIRECTUS_PURPLE, 'Directus Template CLI - Extract'));
|
|
65
|
+
const templateName = await core_1.ux.prompt('What is the name of the template you would like to extract?');
|
|
59
66
|
const directory = await core_1.ux.prompt("What directory would you like to extract the template to? If it doesn't exist, it will be created.", { default: `templates/${(0, slugify_1.default)(templateName, { lower: true, strict: true })}` });
|
|
60
|
-
core_1.ux.log(`You selected ${directory}`);
|
|
61
|
-
|
|
67
|
+
core_1.ux.log(`You selected ${core_1.ux.colorize(constants_1.DIRECTUS_PINK, directory)}`);
|
|
68
|
+
core_1.ux.log(constants_1.SEPARATOR);
|
|
69
|
+
// Get Directus URL
|
|
62
70
|
const directusUrl = await (0, auth_1.getDirectusUrl)();
|
|
63
|
-
const directusToken = await (0, auth_1.getDirectusToken)(directusUrl);
|
|
64
71
|
flags.directusUrl = directusUrl;
|
|
65
|
-
|
|
72
|
+
// Prompt for login method
|
|
73
|
+
const loginMethod = await inquirer_1.default.prompt([
|
|
74
|
+
{
|
|
75
|
+
choices: [
|
|
76
|
+
{ name: 'Directus Access Token', value: 'token' },
|
|
77
|
+
{ name: 'Email and Password', value: 'email' },
|
|
78
|
+
],
|
|
79
|
+
default: 'token',
|
|
80
|
+
message: 'How do you want to log in?',
|
|
81
|
+
name: 'loginMethod',
|
|
82
|
+
type: 'list',
|
|
83
|
+
},
|
|
84
|
+
]);
|
|
85
|
+
if (loginMethod.loginMethod === 'token') {
|
|
86
|
+
const directusToken = await (0, auth_1.getDirectusToken)(directusUrl);
|
|
87
|
+
flags.directusToken = directusToken;
|
|
88
|
+
}
|
|
89
|
+
else {
|
|
90
|
+
flags.userEmail = await core_1.ux.prompt('What is your email?');
|
|
91
|
+
flags.userPassword = await core_1.ux.prompt('What is your password?', { type: 'hide' });
|
|
92
|
+
}
|
|
93
|
+
core_1.ux.log(constants_1.SEPARATOR);
|
|
94
|
+
await this.initializeDirectusApi(flags);
|
|
66
95
|
await this.extractTemplate(templateName, directory, flags);
|
|
67
96
|
}
|
|
68
97
|
async runProgrammatic(flags) {
|
|
@@ -72,8 +101,12 @@ class ExtractCommand extends core_1.Command {
|
|
|
72
101
|
await this.extractTemplate(templateName, templateLocation, flags);
|
|
73
102
|
}
|
|
74
103
|
validateProgrammaticFlags(flags) {
|
|
75
|
-
if (!flags.directusUrl
|
|
76
|
-
core_1.ux.error('Directus URL
|
|
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.');
|
|
77
110
|
}
|
|
78
111
|
if (!flags.templateLocation) {
|
|
79
112
|
core_1.ux.error('Template location is required for programmatic mode.');
|
|
@@ -92,6 +125,7 @@ ExtractCommand.flags = {
|
|
|
92
125
|
directusToken: core_1.Flags.string({
|
|
93
126
|
description: 'Token to use for the Directus instance',
|
|
94
127
|
env: 'SOURCE_DIRECTUS_TOKEN',
|
|
128
|
+
exclusive: ['userEmail', 'userPassword'],
|
|
95
129
|
}),
|
|
96
130
|
directusUrl: core_1.Flags.string({
|
|
97
131
|
description: 'URL of the Directus instance to extract the template from',
|
|
@@ -113,5 +147,17 @@ ExtractCommand.flags = {
|
|
|
113
147
|
description: 'Name of the template',
|
|
114
148
|
env: 'TEMPLATE_NAME',
|
|
115
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
|
+
}),
|
|
116
162
|
};
|
|
117
163
|
exports.default = ExtractCommand;
|
|
@@ -2,9 +2,12 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
const tslib_1 = require("tslib");
|
|
4
4
|
const core_1 = require("@oclif/core");
|
|
5
|
+
const constants_1 = require("../constants");
|
|
5
6
|
const sdk_1 = require("../sdk");
|
|
7
|
+
const catch_error_1 = tslib_1.__importDefault(require("../utils/catch-error"));
|
|
6
8
|
const write_to_file_1 = tslib_1.__importDefault(require("../utils/write-to-file"));
|
|
7
9
|
async function extractAccess(dir) {
|
|
10
|
+
core_1.ux.action.start(core_1.ux.colorize(constants_1.DIRECTUS_PINK, 'Extracting access'));
|
|
8
11
|
try {
|
|
9
12
|
const response = await sdk_1.api.client.request(() => ({
|
|
10
13
|
method: 'GET',
|
|
@@ -15,11 +18,10 @@ async function extractAccess(dir) {
|
|
|
15
18
|
// delete access.id
|
|
16
19
|
// }
|
|
17
20
|
await (0, write_to_file_1.default)('access', response, dir);
|
|
18
|
-
core_1.ux.log('Extracted access');
|
|
19
21
|
}
|
|
20
22
|
catch (error) {
|
|
21
|
-
|
|
22
|
-
core_1.ux.warn(error.message);
|
|
23
|
+
(0, catch_error_1.default)(error);
|
|
23
24
|
}
|
|
25
|
+
core_1.ux.action.stop();
|
|
24
26
|
}
|
|
25
27
|
exports.default = extractAccess;
|