directus-template-cli 0.7.6 → 0.8.0-partials.0
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 +134 -37
- package/dist/commands/apply.d.ts +5 -0
- package/dist/commands/apply.js +32 -68
- package/dist/commands/extract.d.ts +30 -0
- package/dist/commands/extract.js +14 -5
- package/dist/lib/extract/expand-deep-plan.d.ts +2 -0
- package/dist/lib/extract/expand-deep-plan.js +54 -0
- package/dist/lib/extract/expand-schema-plan.d.ts +2 -0
- package/dist/lib/extract/expand-schema-plan.js +55 -0
- package/dist/lib/extract/extract-assets.js +19 -6
- package/dist/lib/extract/extract-collections.d.ts +2 -1
- package/dist/lib/extract/extract-collections.js +5 -2
- package/dist/lib/extract/extract-content.d.ts +2 -1
- package/dist/lib/extract/extract-content.js +105 -12
- package/dist/lib/extract/extract-fields.d.ts +2 -1
- package/dist/lib/extract/extract-fields.js +5 -5
- package/dist/lib/extract/extract-relations.d.ts +2 -1
- package/dist/lib/extract/extract-relations.js +6 -4
- package/dist/lib/extract/index.d.ts +2 -1
- package/dist/lib/extract/index.js +67 -29
- package/dist/lib/load/apply-flags.d.ts +5 -2
- package/dist/lib/load/apply-flags.js +0 -50
- package/dist/lib/load/finalize-collections.d.ts +2 -0
- package/dist/lib/load/finalize-collections.js +28 -0
- package/dist/lib/load/finalize-fields.d.ts +2 -0
- package/dist/lib/load/finalize-fields.js +25 -0
- package/dist/lib/load/index.js +36 -19
- package/dist/lib/load/load-collections.d.ts +2 -1
- package/dist/lib/load/load-collections.js +17 -30
- package/dist/lib/load/load-data.d.ts +2 -1
- package/dist/lib/load/load-data.js +46 -34
- package/dist/lib/load/load-files.js +8 -8
- package/dist/lib/load/load-relations.d.ts +2 -1
- package/dist/lib/load/load-relations.js +17 -7
- package/dist/lib/template-plan/collections.d.ts +4 -0
- package/dist/lib/template-plan/collections.js +26 -0
- package/dist/lib/template-plan/flags.d.ts +18 -0
- package/dist/lib/template-plan/flags.js +61 -0
- package/dist/lib/template-plan/index.d.ts +16 -0
- package/dist/lib/template-plan/index.js +77 -0
- package/dist/lib/template-plan/junctions.d.ts +10 -0
- package/dist/lib/template-plan/junctions.js +19 -0
- package/dist/lib/template-plan/metadata-plan.d.ts +2 -0
- package/dist/lib/template-plan/metadata-plan.js +33 -0
- package/dist/lib/template-plan/metadata.d.ts +5 -0
- package/dist/lib/template-plan/metadata.js +39 -0
- package/dist/lib/template-plan/types.d.ts +34 -0
- package/dist/lib/template-plan/types.js +1 -0
- package/dist/services/github.js +1 -1
- package/oclif.manifest.json +173 -16
- package/package.json +1 -2
- package/dist/lib/load/update-required-fields.d.ts +0 -1
- package/dist/lib/load/update-required-fields.js +0 -20
package/README.md
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
A streamlined CLI tool for creating new Directus projects and managing Directus templates - making it easy to apply and extract template configurations across instances.
|
|
4
4
|
|
|
5
5
|
This tool is best suited for:
|
|
6
|
+
|
|
6
7
|
- Proof of Concept (POC) projects
|
|
7
8
|
- Demo environments
|
|
8
9
|
- New project setups
|
|
@@ -10,8 +11,9 @@ This tool is best suited for:
|
|
|
10
11
|
⚠️ We strongly recommend against using this tool in existing production environments or as a critical part of your CI/CD pipeline without thorough testing. Always create backups before applying templates.
|
|
11
12
|
|
|
12
13
|
**Important Notes:**
|
|
14
|
+
|
|
13
15
|
- **Primary Purpose**: Built to deploy templates created by the Directus Core Team. While community templates are supported, the unlimited possible configurations make comprehensive support challenging.
|
|
14
|
-
- **Database Compatibility**: PostgreSQL is recommended. Applying templates that are extracted and applied between different databases (Extract from SQLite ->
|
|
16
|
+
- **Database Compatibility**: PostgreSQL is recommended. Applying templates that are extracted and applied between different databases (Extract from SQLite -> Apply to Postgres) can caused issues and is not recommended. MySQL users may encounter known issues.
|
|
15
17
|
- **Performance**: Remote operations (extract/apply) are rate-limited to 10 requests/second using bottleneck. Processing time varies based on your instance size (collections, items, assets).
|
|
16
18
|
- **Version Compatibility**:
|
|
17
19
|
- v0.5.0+: Compatible with Directus 11 and up
|
|
@@ -30,6 +32,7 @@ npx directus-template-cli@latest init
|
|
|
30
32
|
```
|
|
31
33
|
|
|
32
34
|
You'll be guided through:
|
|
35
|
+
|
|
33
36
|
- Selecting a directory for your new project
|
|
34
37
|
- Choosing a Directus backend template
|
|
35
38
|
- Selecting a frontend framework (if available for the template)
|
|
@@ -51,7 +54,6 @@ npx directus-template-cli@latest init my-project --frontend=nextjs --template=cm
|
|
|
51
54
|
npx directus-template-cli@latest init --template=https://github.com/directus-labs/starters/tree/main/cms
|
|
52
55
|
```
|
|
53
56
|
|
|
54
|
-
|
|
55
57
|
Available flags:
|
|
56
58
|
|
|
57
59
|
- `--frontend`: Frontend framework to use (e.g., nextjs, nuxt, astro)
|
|
@@ -112,6 +114,7 @@ The `directus:template` property contains:
|
|
|
112
114
|
- Each frontend has a `name` (display name) and `path` (directory containing the frontend code)
|
|
113
115
|
|
|
114
116
|
When you use this template with the `init` command, it will:
|
|
117
|
+
|
|
115
118
|
1. Copy the Directus template files from the specified template directory
|
|
116
119
|
2. Copy the selected frontend code based on your choice or the `--frontend` flag
|
|
117
120
|
3. Set up the project structure with both backend and frontend integrated
|
|
@@ -133,12 +136,10 @@ npx directus-template-cli@latest apply
|
|
|
133
136
|
|
|
134
137
|
You can choose from our community maintained templates or you can also choose a template from a local directory or a public GitHub repository.
|
|
135
138
|
|
|
136
|
-
|
|
137
139
|
### Programmatic Mode
|
|
138
140
|
|
|
139
141
|
By default, the CLI will run in interactive mode. For CI/CD pipelines or automated scripts, you can use the programmatic mode:
|
|
140
142
|
|
|
141
|
-
|
|
142
143
|
Using a token:
|
|
143
144
|
|
|
144
145
|
```
|
|
@@ -151,11 +152,10 @@ Using email/password:
|
|
|
151
152
|
npx directus-template-cli@latest apply -p --directusUrl="http://localhost:8055" --userEmail="admin@example.com" --userPassword="admin" --templateLocation="./my-template" --templateType="local"
|
|
152
153
|
```
|
|
153
154
|
|
|
154
|
-
Partial apply (apply only some
|
|
155
|
+
Partial apply (apply only some parts of a template):
|
|
155
156
|
|
|
156
157
|
```
|
|
157
|
-
npx directus-template-cli@latest apply -p --directusUrl="http://localhost:8055" --userEmail="admin@example.com" --userPassword="your-password" --templateLocation="./my-template" --templateType="local" --
|
|
158
|
-
|
|
158
|
+
npx directus-template-cli@latest apply -p --directusUrl="http://localhost:8055" --userEmail="admin@example.com" --userPassword="your-password" --templateLocation="./my-template" --templateType="local" --schema --permissions --no-content
|
|
159
159
|
```
|
|
160
160
|
|
|
161
161
|
Available flags:
|
|
@@ -166,7 +166,7 @@ Available flags:
|
|
|
166
166
|
- `--userPassword`: Password for Directus authentication (required if not using token)
|
|
167
167
|
- `--templateLocation`: Location of the template to apply (required)
|
|
168
168
|
- `--templateType`: Type of template to apply. Options: community, local, github. Defaults to `local`.
|
|
169
|
-
- `--partial`: Enable partial template
|
|
169
|
+
- `--partial`: Enable partial template mode explicitly. Component and collection flags also imply partial mode.
|
|
170
170
|
- `--content`: Load Content (data)
|
|
171
171
|
- `--dashboards`: Load Dashboards
|
|
172
172
|
- `--extensions`: Load Extensions
|
|
@@ -176,6 +176,11 @@ Available flags:
|
|
|
176
176
|
- `--schema`: Load Schema
|
|
177
177
|
- `--settings`: Load Settings
|
|
178
178
|
- `--users`: Load Users
|
|
179
|
+
- `--collections`: Only apply these comma-separated collections
|
|
180
|
+
- `--exclude-collections`: Exclude these comma-separated collections
|
|
181
|
+
- `--relation-strategy`: How to handle omitted relation targets. Options: `empty`, `preserve`, `deep`.
|
|
182
|
+
- `--allow-broken-relations`: Apply templates that intentionally preserve references to omitted records
|
|
183
|
+
- `--no-assets`: Shorthand for `--no-files` and excluding `directus_files`
|
|
179
184
|
- `--disableTelemetry`: Disable telemetry collection
|
|
180
185
|
|
|
181
186
|
When using `--partial`, you can also use `--no` flags to exclude specific components from being applied. For example:
|
|
@@ -191,34 +196,12 @@ This command will apply the template but exclude content and users. Available `-
|
|
|
191
196
|
- `--no-extensions`: Skip loading Extensions
|
|
192
197
|
- `--no-files`: Skip loading Files
|
|
193
198
|
- `--no-flows`: Skip loading Flows
|
|
194
|
-
- `--no-permissions`: Skip loading
|
|
199
|
+
- `--no-permissions`: Skip loading Permissions
|
|
195
200
|
- `--no-schema`: Skip loading Schema
|
|
196
201
|
- `--no-settings`: Skip loading Settings
|
|
197
202
|
- `--no-users`: Skip loading Users
|
|
198
203
|
|
|
199
|
-
|
|
200
|
-
#### Template Component Dependencies
|
|
201
|
-
|
|
202
|
-
When applying templates, certain components have dependencies on others. Here are the key relationships to be aware of:
|
|
203
|
-
|
|
204
|
-
- `--users`: Depends on `--permissions`. If you include users, permissions will automatically be included.
|
|
205
|
-
- `--permissions`: Depends on `--schema`. If you include permissions, the schema will automatically be included.
|
|
206
|
-
- `--content`: Depends on `--schema`. If you include content, the schema will automatically be included.
|
|
207
|
-
- `--files`: No direct dependencies, but often related to content. Consider including `--content` if you're including files.
|
|
208
|
-
- `--flows`: No direct dependencies, but may interact with other components. Consider your specific use case.
|
|
209
|
-
- `--dashboards`: No direct dependencies, but often rely on data from other components.
|
|
210
|
-
- `--extensions`: No direct dependencies, but may interact with other components.
|
|
211
|
-
- `--settings`: No direct dependencies, but affects the overall system configuration.
|
|
212
|
-
|
|
213
|
-
When using the `--partial` flag, keep these dependencies in mind. For example:
|
|
214
|
-
|
|
215
|
-
```
|
|
216
|
-
npx directus-template-cli@latest apply -p --directusUrl="http://localhost:8055" --directusToken="admin-token-here" --templateLocation="./my-template" --templateType="local" --partial --users
|
|
217
|
-
```
|
|
218
|
-
|
|
219
|
-
This command will automatically include `--permissions` and `--schema` along with `--users`, even if not explicitly specified.
|
|
220
|
-
|
|
221
|
-
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.
|
|
204
|
+
For how partial templates handle schema, content, and relations, see [Partial Templates and Relation Strategies](#partial-templates-and-relation-strategies).
|
|
222
205
|
|
|
223
206
|
#### Using Environment Variables
|
|
224
207
|
|
|
@@ -231,7 +214,6 @@ You can also pass flags as environment variables. This can be useful for CI/CD p
|
|
|
231
214
|
- `TEMPLATE_LOCATION`: Equivalent to `--templateLocation`
|
|
232
215
|
- `TEMPLATE_TYPE`: Equivalent to `--templateType`
|
|
233
216
|
|
|
234
|
-
|
|
235
217
|
### Existing Data
|
|
236
218
|
|
|
237
219
|
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.
|
|
@@ -254,7 +236,9 @@ For data in your own user-created collections, if an item has the same primary k
|
|
|
254
236
|
|
|
255
237
|
The CLI can also extract a template from a Directus instance so that it can be applied to other instances.
|
|
256
238
|
|
|
257
|
-
|
|
239
|
+
Full extraction remains the default. Partial extraction is available with component flags, collection filters, and relation strategies.
|
|
240
|
+
|
|
241
|
+
For how partial templates handle schema, content, and relations, see [Partial Templates and Relation Strategies](#partial-templates-and-relation-strategies).
|
|
258
242
|
|
|
259
243
|
1. Make sure you remove any sensitive data from the Directus instance you don't want to include in the template.
|
|
260
244
|
2. Login and create a Static Access Token for the admin user.
|
|
@@ -289,8 +273,45 @@ Available flags:
|
|
|
289
273
|
- `--userPassword`: Password for Directus authentication (required if not using token)
|
|
290
274
|
- `--templateLocation`: Directory to extract the template to (required)
|
|
291
275
|
- `--templateName`: Name of the template (required)
|
|
276
|
+
- `--partial`: Enable partial template mode explicitly. Component and collection flags also imply partial mode.
|
|
277
|
+
- `--schema` / `--no-schema`: Include or skip schema
|
|
278
|
+
- `--content` / `--no-content`: Include or skip content
|
|
279
|
+
- `--files` / `--no-files`: Include or skip files and assets
|
|
280
|
+
- `--flows` / `--no-flows`: Include or skip flows
|
|
281
|
+
- `--dashboards` / `--no-dashboards`: Include or skip dashboards
|
|
282
|
+
- `--permissions` / `--no-permissions`: Include or skip permissions
|
|
283
|
+
- `--settings` / `--no-settings`: Include or skip settings
|
|
284
|
+
- `--extensions` / `--no-extensions`: Include or skip extensions
|
|
285
|
+
- `--users` / `--no-users`: Include or skip users
|
|
286
|
+
- `--collections`: Only extract these comma-separated collections
|
|
287
|
+
- `--exclude-collections`: Exclude these comma-separated collections
|
|
288
|
+
- `--relation-strategy`: How to handle omitted relation targets. Options: `empty`, `preserve`, `deep`.
|
|
289
|
+
- `--allow-broken-relations`: Mark intentionally incomplete relation references as allowed in metadata
|
|
290
|
+
- `--no-assets`: Shorthand for `--no-files` and excluding `directus_files`
|
|
292
291
|
- `--disableTelemetry`: Disable telemetry collection
|
|
293
292
|
|
|
293
|
+
Examples:
|
|
294
|
+
|
|
295
|
+
`--collections` limits content scope. Schema may still include additional collections needed by the data model.
|
|
296
|
+
|
|
297
|
+
Skip assets safely:
|
|
298
|
+
|
|
299
|
+
```
|
|
300
|
+
npx directus-template-cli@latest extract -p --templateName="My Template" --templateLocation="./my-template" --directusToken="admin-token-here" --directusUrl="http://localhost:8055" --schema --content --collections posts,pages --no-assets --relation-strategy empty
|
|
301
|
+
```
|
|
302
|
+
|
|
303
|
+
Preserve asset IDs but do not export assets:
|
|
304
|
+
|
|
305
|
+
```
|
|
306
|
+
npx directus-template-cli@latest extract -p --templateName="My Template" --templateLocation="./my-template" --directusToken="admin-token-here" --directusUrl="http://localhost:8055" --schema --content --collections posts,pages --no-assets --relation-strategy preserve --allow-broken-relations
|
|
307
|
+
```
|
|
308
|
+
|
|
309
|
+
Portable partial snapshot:
|
|
310
|
+
|
|
311
|
+
```
|
|
312
|
+
npx directus-template-cli@latest extract -p --templateName="My Template" --templateLocation="./my-template" --directusToken="admin-token-here" --directusUrl="http://localhost:8055" --schema --content --collections posts,pages --relation-strategy deep
|
|
313
|
+
```
|
|
314
|
+
|
|
294
315
|
#### Using Environment Variables
|
|
295
316
|
|
|
296
317
|
Similar to the Apply command, you can use environment variables for the Extract command as well:
|
|
@@ -301,14 +322,90 @@ Similar to the Apply command, you can use environment variables for the Extract
|
|
|
301
322
|
- `DIRECTUS_PASSWORD`: Equivalent to `--userPassword`
|
|
302
323
|
- `TEMPLATE_LOCATION`: Equivalent to `--templateLocation`
|
|
303
324
|
|
|
325
|
+
## Partial Templates and Relation Strategies
|
|
326
|
+
|
|
327
|
+
Partial templates can intentionally omit components or collections. The CLI writes and reads `src/template-meta.json` so apply can understand what was extracted.
|
|
328
|
+
|
|
329
|
+
Partial templates have two scopes:
|
|
330
|
+
|
|
331
|
+
- **Schema scope**: the data model needed to keep selected collections valid. This can include related, junction, translation, page-builder, and grouping collections even when no records are exported for them.
|
|
332
|
+
- **Content scope**: the records exported from collections requested with `--collections`, or records added by `deep`.
|
|
333
|
+
|
|
334
|
+
Example: extracting `pages,posts` may still include schema for `page_blocks`, block collections, translations, and groups. Only `pages.json` and `posts.json` are exported unless you use `deep`.
|
|
335
|
+
|
|
336
|
+
Relation strategies:
|
|
337
|
+
|
|
338
|
+
| Strategy | What it exports | What happens to omitted relations | Best for |
|
|
339
|
+
| ---------- | -------------------------- | --------------------------------------------------------------------------- | ------------------------------------------- |
|
|
340
|
+
| `empty` | Selected content only | M2O fields become `null`; alias/O2M/M2M/M2A fields are omitted from records | Clean subsets, skipping assets safely |
|
|
341
|
+
| `preserve` | Selected content only | Keeps IDs/arrays as-is and writes warnings | Targets where related records already exist |
|
|
342
|
+
| `deep` | Selected + related content | Keeps relation values and recursively exports related records | Portable partial snapshots |
|
|
343
|
+
|
|
344
|
+
Tradeoffs:
|
|
345
|
+
|
|
346
|
+
- `empty` produces directly applyable templates, but applying to an existing instance can clear omitted M2O references.
|
|
347
|
+
- `preserve` can intentionally leave references to records that are not in the template. Apply requires `--allow-broken-relations` when metadata reports these references.
|
|
348
|
+
- `deep` is the most portable, but can expand into many collections and export much more data.
|
|
349
|
+
|
|
350
|
+
For example, a source record might contain both a file relation and a page-builder alias field:
|
|
351
|
+
|
|
352
|
+
```json
|
|
353
|
+
{
|
|
354
|
+
"id": "post-1",
|
|
355
|
+
"image": "file-uuid",
|
|
356
|
+
"blocks": ["block-1", "block-2"]
|
|
357
|
+
}
|
|
358
|
+
```
|
|
359
|
+
|
|
360
|
+
With `--relation-strategy empty`, omitted relations are cleared or skipped:
|
|
361
|
+
|
|
362
|
+
```json
|
|
363
|
+
{
|
|
364
|
+
"id": "post-1",
|
|
365
|
+
"image": null
|
|
366
|
+
}
|
|
367
|
+
```
|
|
368
|
+
|
|
369
|
+
With `--relation-strategy preserve`, relation values stay in the record and metadata records warnings:
|
|
370
|
+
|
|
371
|
+
```json
|
|
372
|
+
{
|
|
373
|
+
"id": "post-1",
|
|
374
|
+
"image": "file-uuid",
|
|
375
|
+
"blocks": ["block-1", "block-2"]
|
|
376
|
+
}
|
|
377
|
+
```
|
|
378
|
+
|
|
379
|
+
With `--relation-strategy deep`, the record stays the same and related content collections are exported too:
|
|
380
|
+
|
|
381
|
+
```jsonc
|
|
382
|
+
// content/posts.json
|
|
383
|
+
{
|
|
384
|
+
"id": "post-1",
|
|
385
|
+
"image": "file-uuid",
|
|
386
|
+
"blocks": ["block-1", "block-2"],
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
// also exported:
|
|
390
|
+
// content/page_blocks.json
|
|
391
|
+
// content/block_hero.json
|
|
392
|
+
```
|
|
393
|
+
|
|
394
|
+
Skip assets safely:
|
|
395
|
+
|
|
396
|
+
```
|
|
397
|
+
npx directus-template-cli@latest apply -p --directusUrl="http://localhost:8055" --directusToken="admin-token-here" --templateLocation="./my-template" --templateType="local" --content --collections posts,pages --no-assets --relation-strategy empty
|
|
398
|
+
```
|
|
399
|
+
|
|
304
400
|
## Logs
|
|
305
401
|
|
|
306
402
|
The Directus Template CLI logs information to a file in the `.directus-template-cli/logs` directory.
|
|
307
403
|
|
|
308
404
|
Logs are automatically generated for each run of the CLI. Here's how the logging system works:
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
405
|
+
|
|
406
|
+
- A new log file is created for each CLI run.
|
|
407
|
+
- Log files are stored in the `.directus-template-cli/logs` directory within your current working directory.
|
|
408
|
+
- Each log file is named `run-[timestamp].log`, where `[timestamp]` is the ISO timestamp of when the CLI was initiated.
|
|
312
409
|
|
|
313
410
|
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.
|
|
314
411
|
|
package/dist/commands/apply.d.ts
CHANGED
|
@@ -3,18 +3,23 @@ export default class ApplyCommand extends BaseCommand {
|
|
|
3
3
|
static description: string;
|
|
4
4
|
static examples: string[];
|
|
5
5
|
static flags: {
|
|
6
|
+
allowBrokenRelations: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
7
|
+
collections: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
6
8
|
content: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
7
9
|
dashboards: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
8
10
|
directusToken: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
9
11
|
directusUrl: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
10
12
|
disableTelemetry: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
13
|
+
excludeCollections: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
11
14
|
extensions: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
12
15
|
files: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
13
16
|
flows: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
17
|
+
noAssets: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
14
18
|
noExit: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
15
19
|
partial: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
16
20
|
permissions: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
17
21
|
programmatic: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
22
|
+
relationStrategy: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
18
23
|
schema: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
19
24
|
settings: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
20
25
|
templateLocation: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
package/dist/commands/apply.js
CHANGED
|
@@ -3,13 +3,14 @@ import { Flags, ux } from '@oclif/core';
|
|
|
3
3
|
import chalk from 'chalk';
|
|
4
4
|
import * as path from 'pathe';
|
|
5
5
|
import * as customFlags from '../flags/common.js';
|
|
6
|
-
import { BSL_LICENSE_CTA, BSL_LICENSE_HEADLINE, BSL_LICENSE_TEXT, DIRECTUS_PINK, DIRECTUS_PURPLE, SEPARATOR } from '../lib/constants.js';
|
|
7
|
-
import {
|
|
6
|
+
import { BSL_LICENSE_CTA, BSL_LICENSE_HEADLINE, BSL_LICENSE_TEXT, DIRECTUS_PINK, DIRECTUS_PURPLE, SEPARATOR, } from '../lib/constants.js';
|
|
7
|
+
import { validateProgrammaticFlags } from '../lib/load/apply-flags.js';
|
|
8
8
|
import apply from '../lib/load/index.js';
|
|
9
|
+
import * as templatePlanFlags from '../lib/template-plan/flags.js';
|
|
9
10
|
import { animatedBunny } from '../lib/utils/animated-bunny.js';
|
|
10
|
-
import { getDirectusEmailAndPassword, getDirectusToken, getDirectusUrl, initializeDirectusApi } from '../lib/utils/auth.js';
|
|
11
|
+
import { getDirectusEmailAndPassword, getDirectusToken, getDirectusUrl, initializeDirectusApi, } from '../lib/utils/auth.js';
|
|
11
12
|
import catchError from '../lib/utils/catch-error.js';
|
|
12
|
-
import { getCommunityTemplates, getGithubTemplate, getInteractiveLocalTemplate, getLocalTemplate } from '../lib/utils/get-template.js';
|
|
13
|
+
import { getCommunityTemplates, getGithubTemplate, getInteractiveLocalTemplate, getLocalTemplate, } from '../lib/utils/get-template.js';
|
|
13
14
|
import { logger } from '../lib/utils/logger.js';
|
|
14
15
|
import openUrl from '../lib/utils/open-url.js';
|
|
15
16
|
import { shutdown, track } from '../services/posthog.js';
|
|
@@ -22,60 +23,28 @@ export default class ApplyCommand extends BaseCommand {
|
|
|
22
23
|
'$ directus-template-cli@beta apply -p --directusUrl="http://localhost:8055" --directusToken="admin-token-here" --templateLocation="./my-template" --templateType="local" --partial --no-content --no-users',
|
|
23
24
|
];
|
|
24
25
|
static flags = {
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
}),
|
|
30
|
-
dashboards: Flags.boolean({
|
|
31
|
-
allowNo: true,
|
|
32
|
-
default: undefined,
|
|
33
|
-
description: 'Load Dashboards (dashboards, panels)',
|
|
34
|
-
}),
|
|
26
|
+
allowBrokenRelations: templatePlanFlags.allowBrokenRelations,
|
|
27
|
+
collections: templatePlanFlags.collections,
|
|
28
|
+
content: templatePlanFlags.componentFlags.content,
|
|
29
|
+
dashboards: templatePlanFlags.componentFlags.dashboards,
|
|
35
30
|
directusToken: customFlags.directusToken,
|
|
36
31
|
directusUrl: customFlags.directusUrl,
|
|
37
32
|
disableTelemetry: customFlags.disableTelemetry,
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
files: Flags.boolean({
|
|
44
|
-
allowNo: true,
|
|
45
|
-
default: undefined,
|
|
46
|
-
description: 'Load Files (files, folders)',
|
|
47
|
-
}),
|
|
48
|
-
flows: Flags.boolean({
|
|
49
|
-
allowNo: true,
|
|
50
|
-
default: undefined,
|
|
51
|
-
description: 'Load Flows (operations, flows)',
|
|
52
|
-
}),
|
|
33
|
+
excludeCollections: templatePlanFlags.excludeCollections,
|
|
34
|
+
extensions: templatePlanFlags.componentFlags.extensions,
|
|
35
|
+
files: templatePlanFlags.componentFlags.files,
|
|
36
|
+
flows: templatePlanFlags.componentFlags.flows,
|
|
37
|
+
noAssets: templatePlanFlags.noAssets,
|
|
53
38
|
noExit: Flags.boolean({
|
|
54
39
|
default: false,
|
|
55
40
|
hidden: true,
|
|
56
41
|
}),
|
|
57
|
-
partial:
|
|
58
|
-
|
|
59
|
-
description: 'Enable partial template application (all components enabled by default)',
|
|
60
|
-
summary: 'Enable partial template application',
|
|
61
|
-
}),
|
|
62
|
-
permissions: Flags.boolean({
|
|
63
|
-
allowNo: true,
|
|
64
|
-
default: undefined,
|
|
65
|
-
description: 'Loads permissions data. Collections include: directus_roles, directus_policies, directus_access, directus_permissions.',
|
|
66
|
-
summary: 'Load permissions (roles, policies, access, permissions)',
|
|
67
|
-
}),
|
|
42
|
+
partial: templatePlanFlags.partial,
|
|
43
|
+
permissions: templatePlanFlags.componentFlags.permissions,
|
|
68
44
|
programmatic: customFlags.programmatic,
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
description: 'Load schema (collections, relations)',
|
|
73
|
-
}),
|
|
74
|
-
settings: Flags.boolean({
|
|
75
|
-
allowNo: true,
|
|
76
|
-
default: undefined,
|
|
77
|
-
description: 'Load settings (project settings, translations, presets)',
|
|
78
|
-
}),
|
|
45
|
+
relationStrategy: templatePlanFlags.relationStrategy,
|
|
46
|
+
schema: templatePlanFlags.componentFlags.schema,
|
|
47
|
+
settings: templatePlanFlags.componentFlags.settings,
|
|
79
48
|
templateLocation: customFlags.templateLocation,
|
|
80
49
|
templateType: Flags.string({
|
|
81
50
|
default: 'local',
|
|
@@ -87,11 +56,7 @@ export default class ApplyCommand extends BaseCommand {
|
|
|
87
56
|
}),
|
|
88
57
|
userEmail: customFlags.userEmail,
|
|
89
58
|
userPassword: customFlags.userPassword,
|
|
90
|
-
users:
|
|
91
|
-
allowNo: true,
|
|
92
|
-
default: undefined,
|
|
93
|
-
description: 'Load users',
|
|
94
|
-
}),
|
|
59
|
+
users: templatePlanFlags.componentFlags.users,
|
|
95
60
|
};
|
|
96
61
|
/**
|
|
97
62
|
* MAIN
|
|
@@ -110,9 +75,9 @@ export default class ApplyCommand extends BaseCommand {
|
|
|
110
75
|
* @returns {Promise<void>} - Returns nothing
|
|
111
76
|
*/
|
|
112
77
|
async runInteractive(flags) {
|
|
113
|
-
const validatedFlags =
|
|
78
|
+
const validatedFlags = flags;
|
|
114
79
|
// Show animated intro
|
|
115
|
-
await animatedBunny(
|
|
80
|
+
await animatedBunny("Let's apply a template!");
|
|
116
81
|
intro(`${chalk.bgHex(DIRECTUS_PURPLE).white.bold('Directus Template CLI')} - Apply Template`);
|
|
117
82
|
const templateType = await select({
|
|
118
83
|
message: 'What type of template would you like to apply?',
|
|
@@ -129,11 +94,18 @@ export default class ApplyCommand extends BaseCommand {
|
|
|
129
94
|
const templates = await getCommunityTemplates();
|
|
130
95
|
const selectedTemplate = await select({
|
|
131
96
|
message: 'Select a template.',
|
|
132
|
-
options: templates.map(t => ({ label: t.templateName, value: t })),
|
|
97
|
+
options: templates.map((t) => ({ label: t.templateName, value: t })),
|
|
133
98
|
});
|
|
134
99
|
template = selectedTemplate;
|
|
135
100
|
break;
|
|
136
101
|
}
|
|
102
|
+
case 'directus-plus': {
|
|
103
|
+
openUrl('https://directus.io/plus?utm_source=directus-template-cli&utm_content=apply-command');
|
|
104
|
+
log.info('Redirecting to Directus website.');
|
|
105
|
+
if (!validatedFlags.noExit)
|
|
106
|
+
process.exit(0);
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
137
109
|
case 'github': {
|
|
138
110
|
const ghTemplateUrl = await text({
|
|
139
111
|
message: 'What is the public GitHub repository URL?',
|
|
@@ -148,13 +120,6 @@ export default class ApplyCommand extends BaseCommand {
|
|
|
148
120
|
template = await this.selectLocalTemplate(localTemplateDir);
|
|
149
121
|
break;
|
|
150
122
|
}
|
|
151
|
-
case 'directus-plus': {
|
|
152
|
-
openUrl('https://directus.io/plus?utm_source=directus-template-cli&utm_content=apply-command');
|
|
153
|
-
log.info('Redirecting to Directus website.');
|
|
154
|
-
if (!validatedFlags.noExit)
|
|
155
|
-
process.exit(0);
|
|
156
|
-
return;
|
|
157
|
-
}
|
|
158
123
|
}
|
|
159
124
|
log.info(`You selected ${ux.colorize(DIRECTUS_PINK, template.templateName)}`);
|
|
160
125
|
// Get Directus URL
|
|
@@ -222,7 +187,6 @@ export default class ApplyCommand extends BaseCommand {
|
|
|
222
187
|
ux.stdout('Template applied successfully.');
|
|
223
188
|
if (!validatedFlags.noExit)
|
|
224
189
|
process.exit(0);
|
|
225
|
-
return;
|
|
226
190
|
}
|
|
227
191
|
}
|
|
228
192
|
/**
|
|
@@ -237,7 +201,7 @@ export default class ApplyCommand extends BaseCommand {
|
|
|
237
201
|
switch (validatedFlags.templateType) {
|
|
238
202
|
case 'community': {
|
|
239
203
|
const templates = await getCommunityTemplates();
|
|
240
|
-
template = templates.find(t => t.templateName === validatedFlags.templateLocation) || templates[0];
|
|
204
|
+
template = templates.find((t) => t.templateName === validatedFlags.templateLocation) || templates[0];
|
|
241
205
|
break;
|
|
242
206
|
}
|
|
243
207
|
case 'github': {
|
|
@@ -315,7 +279,7 @@ export default class ApplyCommand extends BaseCommand {
|
|
|
315
279
|
}
|
|
316
280
|
const selectedTemplate = await select({
|
|
317
281
|
message: 'Multiple templates found. Please select one:',
|
|
318
|
-
options: templates.map(t => ({
|
|
282
|
+
options: templates.map((t) => ({
|
|
319
283
|
label: `${t.templateName} (${path.basename(t.directoryPath)})`,
|
|
320
284
|
value: t,
|
|
321
285
|
})),
|
|
@@ -1,21 +1,51 @@
|
|
|
1
1
|
import { BaseCommand } from './base.js';
|
|
2
2
|
export interface ExtractFlags {
|
|
3
|
+
allowBrokenRelations?: boolean;
|
|
4
|
+
collections?: string;
|
|
5
|
+
content?: boolean;
|
|
6
|
+
dashboards?: boolean;
|
|
3
7
|
directusToken: string;
|
|
4
8
|
directusUrl: string;
|
|
5
9
|
disableTelemetry?: boolean;
|
|
10
|
+
excludeCollections?: string;
|
|
11
|
+
extensions?: boolean;
|
|
12
|
+
files?: boolean;
|
|
13
|
+
flows?: boolean;
|
|
14
|
+
noAssets?: boolean;
|
|
15
|
+
partial?: boolean;
|
|
16
|
+
permissions?: boolean;
|
|
6
17
|
programmatic: boolean;
|
|
18
|
+
relationStrategy?: 'deep' | 'empty' | 'preserve';
|
|
19
|
+
schema?: boolean;
|
|
20
|
+
settings?: boolean;
|
|
7
21
|
templateLocation: string;
|
|
8
22
|
templateName: string;
|
|
9
23
|
userEmail: string;
|
|
10
24
|
userPassword: string;
|
|
25
|
+
users?: boolean;
|
|
11
26
|
}
|
|
12
27
|
export default class ExtractCommand extends BaseCommand {
|
|
13
28
|
static description: string;
|
|
14
29
|
static examples: string[];
|
|
15
30
|
static flags: {
|
|
31
|
+
allowBrokenRelations: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
32
|
+
collections: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
33
|
+
excludeCollections: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
34
|
+
noAssets: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
35
|
+
relationStrategy: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
36
|
+
content: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
37
|
+
dashboards: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
38
|
+
extensions: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
39
|
+
files: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
40
|
+
flows: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
41
|
+
permissions: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
42
|
+
schema: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
43
|
+
settings: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
44
|
+
users: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
16
45
|
directusToken: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
17
46
|
directusUrl: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
18
47
|
disableTelemetry: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
48
|
+
partial: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
19
49
|
programmatic: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
20
50
|
templateLocation: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
21
51
|
templateName: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
package/dist/commands/extract.js
CHANGED
|
@@ -5,12 +5,14 @@ import chalk from 'chalk';
|
|
|
5
5
|
import fs from 'node:fs';
|
|
6
6
|
import path from 'pathe';
|
|
7
7
|
import * as customFlags from '../flags/common.js';
|
|
8
|
-
import { BSL_LICENSE_CTA, BSL_LICENSE_HEADLINE, BSL_LICENSE_TEXT, DIRECTUS_PINK, DIRECTUS_PURPLE, SEPARATOR } from '../lib/constants.js';
|
|
8
|
+
import { BSL_LICENSE_CTA, BSL_LICENSE_HEADLINE, BSL_LICENSE_TEXT, DIRECTUS_PINK, DIRECTUS_PURPLE, SEPARATOR, } from '../lib/constants.js';
|
|
9
9
|
import extract from '../lib/extract/index.js';
|
|
10
|
+
import * as templatePlanFlags from '../lib/template-plan/flags.js';
|
|
11
|
+
import { buildTemplatePlan } from '../lib/template-plan/index.js';
|
|
10
12
|
import { animatedBunny } from '../lib/utils/animated-bunny.js';
|
|
11
|
-
import { getDirectusEmailAndPassword, getDirectusToken, getDirectusUrl, initializeDirectusApi, validateAuthFlags } from '../lib/utils/auth.js';
|
|
13
|
+
import { getDirectusEmailAndPassword, getDirectusToken, getDirectusUrl, initializeDirectusApi, validateAuthFlags, } from '../lib/utils/auth.js';
|
|
12
14
|
import catchError from '../lib/utils/catch-error.js';
|
|
13
|
-
import { generatePackageJsonContent, generateReadmeContent
|
|
15
|
+
import { generatePackageJsonContent, generateReadmeContent } from '../lib/utils/template-defaults.js';
|
|
14
16
|
import { shutdown, track } from '../services/posthog.js';
|
|
15
17
|
import { BaseCommand } from './base.js';
|
|
16
18
|
export default class ExtractCommand extends BaseCommand {
|
|
@@ -23,11 +25,18 @@ export default class ExtractCommand extends BaseCommand {
|
|
|
23
25
|
directusToken: customFlags.directusToken,
|
|
24
26
|
directusUrl: customFlags.directusUrl,
|
|
25
27
|
disableTelemetry: customFlags.disableTelemetry,
|
|
28
|
+
partial: templatePlanFlags.partial,
|
|
26
29
|
programmatic: customFlags.programmatic,
|
|
27
30
|
templateLocation: customFlags.templateLocation,
|
|
28
31
|
templateName: customFlags.templateName,
|
|
29
32
|
userEmail: customFlags.userEmail,
|
|
30
33
|
userPassword: customFlags.userPassword,
|
|
34
|
+
...templatePlanFlags.componentFlags,
|
|
35
|
+
allowBrokenRelations: templatePlanFlags.allowBrokenRelations,
|
|
36
|
+
collections: templatePlanFlags.collections,
|
|
37
|
+
excludeCollections: templatePlanFlags.excludeCollections,
|
|
38
|
+
noAssets: templatePlanFlags.noAssets,
|
|
39
|
+
relationStrategy: templatePlanFlags.relationStrategy,
|
|
31
40
|
};
|
|
32
41
|
/**
|
|
33
42
|
* Main run method for the ExtractCommand
|
|
@@ -82,7 +91,7 @@ export default class ExtractCommand extends BaseCommand {
|
|
|
82
91
|
}
|
|
83
92
|
ux.stdout(SEPARATOR);
|
|
84
93
|
ux.action.start(`Extracting template - ${ux.colorize(DIRECTUS_PINK, templateName)} from ${ux.colorize(DIRECTUS_PINK, flags.directusUrl)} to ${ux.colorize(DIRECTUS_PINK, directory)}`);
|
|
85
|
-
await extract(directory);
|
|
94
|
+
await extract(directory, buildTemplatePlan(flags));
|
|
86
95
|
ux.action.stop();
|
|
87
96
|
// Track completion before final messages/exit
|
|
88
97
|
if (!flags.disableTelemetry) {
|
|
@@ -114,7 +123,7 @@ export default class ExtractCommand extends BaseCommand {
|
|
|
114
123
|
* @returns {Promise<void>} - Returns nothing
|
|
115
124
|
*/
|
|
116
125
|
async runInteractive(flags) {
|
|
117
|
-
await animatedBunny(
|
|
126
|
+
await animatedBunny("Let's extract a template!");
|
|
118
127
|
intro(`${chalk.bgHex(DIRECTUS_PURPLE).white.bold('Directus Template CLI')} - Extract Template`);
|
|
119
128
|
const templateName = await text({
|
|
120
129
|
message: 'What is the name of the template you would like to extract?',
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { readCollections, readRelations } from '@directus/sdk';
|
|
2
|
+
import { ux } from '@oclif/core';
|
|
3
|
+
import { api } from '../sdk.js';
|
|
4
|
+
import { includesCollection } from '../template-plan/index.js';
|
|
5
|
+
export async function expandDeepPlan(plan) {
|
|
6
|
+
if (!plan.partial || plan.relationStrategy !== 'deep' || !plan.collections)
|
|
7
|
+
return plan;
|
|
8
|
+
const collections = await api.client.request(readCollections());
|
|
9
|
+
const availableCollections = collections
|
|
10
|
+
.filter((collection) => !collection.collection.startsWith('directus_', 0))
|
|
11
|
+
.filter((collection) => collection.schema !== null)
|
|
12
|
+
.map((collection) => collection.collection)
|
|
13
|
+
.filter((collection) => includesCollection(collection, { ...plan, collections: undefined }));
|
|
14
|
+
const available = new Set(availableCollections);
|
|
15
|
+
const missingCollections = plan.collections.filter((collection) => !available.has(collection));
|
|
16
|
+
if (missingCollections.length > 0) {
|
|
17
|
+
ux.warn(`Requested collections not found or excluded: ${missingCollections.join(', ')}`);
|
|
18
|
+
}
|
|
19
|
+
const selected = new Set(plan.collections.filter((collection) => available.has(collection)));
|
|
20
|
+
const relations = (await api.client.request(readRelations()));
|
|
21
|
+
let changed = true;
|
|
22
|
+
while (changed) {
|
|
23
|
+
changed = false;
|
|
24
|
+
const candidates = [];
|
|
25
|
+
for (const relation of relations) {
|
|
26
|
+
if (selected.has(relation.collection) && relation.related_collection) {
|
|
27
|
+
candidates.push(relation.related_collection);
|
|
28
|
+
}
|
|
29
|
+
if (relation.related_collection && selected.has(relation.related_collection)) {
|
|
30
|
+
candidates.push(relation.collection);
|
|
31
|
+
}
|
|
32
|
+
if (selected.has(relation.collection) && relation.meta?.one_allowed_collections) {
|
|
33
|
+
candidates.push(...relation.meta.one_allowed_collections);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
for (const collection of candidates) {
|
|
37
|
+
if (!available.has(collection))
|
|
38
|
+
continue;
|
|
39
|
+
if (selected.has(collection))
|
|
40
|
+
continue;
|
|
41
|
+
selected.add(collection);
|
|
42
|
+
changed = true;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
const expandedCollections = [...selected];
|
|
46
|
+
const addedCollections = expandedCollections.filter((collection) => !plan.collections?.includes(collection));
|
|
47
|
+
if (addedCollections.length > 0) {
|
|
48
|
+
ux.warn(`Deep relation strategy expanded collections: ${addedCollections.join(', ')}`);
|
|
49
|
+
}
|
|
50
|
+
return {
|
|
51
|
+
...plan,
|
|
52
|
+
collections: expandedCollections,
|
|
53
|
+
};
|
|
54
|
+
}
|