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.
Files changed (53) hide show
  1. package/README.md +134 -37
  2. package/dist/commands/apply.d.ts +5 -0
  3. package/dist/commands/apply.js +32 -68
  4. package/dist/commands/extract.d.ts +30 -0
  5. package/dist/commands/extract.js +14 -5
  6. package/dist/lib/extract/expand-deep-plan.d.ts +2 -0
  7. package/dist/lib/extract/expand-deep-plan.js +54 -0
  8. package/dist/lib/extract/expand-schema-plan.d.ts +2 -0
  9. package/dist/lib/extract/expand-schema-plan.js +55 -0
  10. package/dist/lib/extract/extract-assets.js +19 -6
  11. package/dist/lib/extract/extract-collections.d.ts +2 -1
  12. package/dist/lib/extract/extract-collections.js +5 -2
  13. package/dist/lib/extract/extract-content.d.ts +2 -1
  14. package/dist/lib/extract/extract-content.js +105 -12
  15. package/dist/lib/extract/extract-fields.d.ts +2 -1
  16. package/dist/lib/extract/extract-fields.js +5 -5
  17. package/dist/lib/extract/extract-relations.d.ts +2 -1
  18. package/dist/lib/extract/extract-relations.js +6 -4
  19. package/dist/lib/extract/index.d.ts +2 -1
  20. package/dist/lib/extract/index.js +67 -29
  21. package/dist/lib/load/apply-flags.d.ts +5 -2
  22. package/dist/lib/load/apply-flags.js +0 -50
  23. package/dist/lib/load/finalize-collections.d.ts +2 -0
  24. package/dist/lib/load/finalize-collections.js +28 -0
  25. package/dist/lib/load/finalize-fields.d.ts +2 -0
  26. package/dist/lib/load/finalize-fields.js +25 -0
  27. package/dist/lib/load/index.js +36 -19
  28. package/dist/lib/load/load-collections.d.ts +2 -1
  29. package/dist/lib/load/load-collections.js +17 -30
  30. package/dist/lib/load/load-data.d.ts +2 -1
  31. package/dist/lib/load/load-data.js +46 -34
  32. package/dist/lib/load/load-files.js +8 -8
  33. package/dist/lib/load/load-relations.d.ts +2 -1
  34. package/dist/lib/load/load-relations.js +17 -7
  35. package/dist/lib/template-plan/collections.d.ts +4 -0
  36. package/dist/lib/template-plan/collections.js +26 -0
  37. package/dist/lib/template-plan/flags.d.ts +18 -0
  38. package/dist/lib/template-plan/flags.js +61 -0
  39. package/dist/lib/template-plan/index.d.ts +16 -0
  40. package/dist/lib/template-plan/index.js +77 -0
  41. package/dist/lib/template-plan/junctions.d.ts +10 -0
  42. package/dist/lib/template-plan/junctions.js +19 -0
  43. package/dist/lib/template-plan/metadata-plan.d.ts +2 -0
  44. package/dist/lib/template-plan/metadata-plan.js +33 -0
  45. package/dist/lib/template-plan/metadata.d.ts +5 -0
  46. package/dist/lib/template-plan/metadata.js +39 -0
  47. package/dist/lib/template-plan/types.d.ts +34 -0
  48. package/dist/lib/template-plan/types.js +1 -0
  49. package/dist/services/github.js +1 -1
  50. package/oclif.manifest.json +173 -16
  51. package/package.json +1 -2
  52. package/dist/lib/load/update-required-fields.d.ts +0 -1
  53. package/dist/lib/load/update-required-fields.js +0 -20
@@ -0,0 +1,2 @@
1
+ import { type TemplatePlan } from '../template-plan/index.js';
2
+ export declare function expandSchemaPlan(plan: TemplatePlan): Promise<TemplatePlan>;
@@ -0,0 +1,55 @@
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 expandSchemaPlan(plan) {
6
+ if (!plan.partial || !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
+ .map((collection) => collection.collection)
12
+ .filter((collection) => includesCollection(collection, { ...plan, collections: undefined }));
13
+ const collectionMap = new Map(collections.map((collection) => [collection.collection, collection]));
14
+ const available = new Set(availableCollections);
15
+ const selected = new Set(plan.collections.filter((collection) => available.has(collection)));
16
+ const relations = (await api.client.request(readRelations()));
17
+ let changed = true;
18
+ while (changed) {
19
+ changed = false;
20
+ const candidates = [];
21
+ for (const collection of selected) {
22
+ const group = collectionMap.get(collection)?.meta?.group;
23
+ if (group)
24
+ candidates.push(group);
25
+ }
26
+ for (const relation of relations) {
27
+ if (selected.has(relation.collection) && relation.related_collection) {
28
+ candidates.push(relation.related_collection);
29
+ }
30
+ if (relation.related_collection && selected.has(relation.related_collection)) {
31
+ candidates.push(relation.collection);
32
+ }
33
+ if (selected.has(relation.collection) && relation.meta?.one_allowed_collections) {
34
+ candidates.push(...relation.meta.one_allowed_collections);
35
+ }
36
+ }
37
+ for (const collection of candidates) {
38
+ if (!available.has(collection))
39
+ continue;
40
+ if (selected.has(collection))
41
+ continue;
42
+ selected.add(collection);
43
+ changed = true;
44
+ }
45
+ }
46
+ const schemaCollections = [...selected];
47
+ const addedCollections = schemaCollections.filter((collection) => !plan.collections?.includes(collection));
48
+ if (addedCollections.length > 0) {
49
+ ux.warn(`Schema scope expanded collections: ${addedCollections.join(', ')}`);
50
+ }
51
+ return {
52
+ ...plan,
53
+ schemaCollections,
54
+ };
55
+ }
@@ -5,10 +5,13 @@ import path from 'pathe';
5
5
  import { DIRECTUS_PINK } from '../constants.js';
6
6
  import { api } from '../sdk.js';
7
7
  import catchError from '../utils/catch-error.js';
8
- async function getAssetList() {
9
- return api.client.request(readFiles({ limit: -1 }));
8
+ // Keep asset pages conservative because each item may trigger a binary download.
9
+ const PAGE_SIZE = 100;
10
+ async function getAssetPage(page) {
11
+ return api.client.request(readFiles({ limit: PAGE_SIZE, page }));
10
12
  }
11
13
  async function downloadFile(file, dir) {
14
+ // eslint-disable-next-line n/no-unsupported-features/node-builtins
12
15
  const response = await api.client.request(() => ({
13
16
  method: 'GET',
14
17
  path: `/assets/${file.id}`,
@@ -29,10 +32,20 @@ export async function downloadAllFiles(dir) {
29
32
  if (path && !fs.existsSync(fullPath)) {
30
33
  fs.mkdirSync(fullPath, { recursive: true });
31
34
  }
32
- const fileList = await getAssetList();
33
- await Promise.all(fileList.map(file => downloadFile(file, dir).catch(error => {
34
- catchError(`Error downloading ${file.filename_disk}: ${error.message}`);
35
- })));
35
+ let page = 1;
36
+ while (true) {
37
+ ux.action.status = `Downloading assets page ${page}`;
38
+ // Page asset metadata sequentially and finish each page before fetching the next, to avoid queuing all downloads at once.
39
+ // eslint-disable-next-line no-await-in-loop
40
+ const fileList = await getAssetPage(page);
41
+ // eslint-disable-next-line no-await-in-loop
42
+ await Promise.all(fileList.map((file) => downloadFile(file, dir).catch((error) => {
43
+ catchError(`Error downloading ${file.filename_disk}: ${error.message}`);
44
+ })));
45
+ if (fileList.length < PAGE_SIZE)
46
+ break;
47
+ page++;
48
+ }
36
49
  }
37
50
  catch (error) {
38
51
  catchError(error);
@@ -1,4 +1,5 @@
1
+ import { type TemplatePlan } from '../template-plan/index.js';
1
2
  /**
2
3
  * Extract collections from the Directus instance
3
4
  */
4
- export default function extractCollections(dir: string): Promise<void>;
5
+ export default function extractCollections(dir: string, plan?: TemplatePlan): Promise<void>;
@@ -2,16 +2,19 @@ import { readCollections } from '@directus/sdk';
2
2
  import { ux } from '@oclif/core';
3
3
  import { DIRECTUS_PINK } from '../constants.js';
4
4
  import { api } from '../sdk.js';
5
+ import { includesSchemaCollection } from '../template-plan/index.js';
5
6
  import catchError from '../utils/catch-error.js';
6
7
  import writeToFile from '../utils/write-to-file.js';
7
8
  /**
8
9
  * Extract collections from the Directus instance
9
10
  */
10
- export default async function extractCollections(dir) {
11
+ export default async function extractCollections(dir, plan) {
11
12
  ux.action.start(ux.colorize(DIRECTUS_PINK, 'Extracting collections'));
12
13
  try {
13
14
  const response = await api.client.request(readCollections());
14
- const collections = response.filter(collection => !collection.collection.startsWith('directus_'));
15
+ const collections = response
16
+ .filter((collection) => !collection.collection.startsWith('directus_'))
17
+ .filter((collection) => includesSchemaCollection(collection.collection, plan));
15
18
  await writeToFile('collections', collections, dir);
16
19
  }
17
20
  catch (error) {
@@ -1 +1,2 @@
1
- export declare function extractContent(dir: string): Promise<void>;
1
+ import { type TemplatePlan, type TemplateWarning } from '../template-plan/index.js';
2
+ export declare function extractContent(dir: string, plan?: TemplatePlan): Promise<TemplateWarning[]>;
@@ -1,33 +1,126 @@
1
- import { readCollections, readItems } from '@directus/sdk';
1
+ import { readCollections, readItems, readRelations } from '@directus/sdk';
2
2
  import { ux } from '@oclif/core';
3
3
  import { DIRECTUS_PINK } from '../constants.js';
4
4
  import { api } from '../sdk.js';
5
+ import { getBrokenJunctionCollections, includesCollection, } from '../template-plan/index.js';
5
6
  import catchError from '../utils/catch-error.js';
6
7
  import writeToFile from '../utils/write-to-file.js';
7
- async function getCollections() {
8
+ // Content items are JSON-only, so pages can be larger than asset download pages.
9
+ const PAGE_SIZE = 500;
10
+ async function getCollections(relations, plan) {
8
11
  const response = await api.client.request(readCollections());
12
+ const brokenJunctions = getBrokenJunctionCollections(relations, plan);
9
13
  return response
10
- .filter(item => !item.collection.startsWith('directus_', 0))
11
- .filter(item => item.schema != null)
12
- .map(i => i.collection);
14
+ .filter((item) => !item.collection.startsWith('directus_', 0))
15
+ .filter((item) => item.schema !== null)
16
+ .map((i) => i.collection)
17
+ .filter((collection) => includesCollection(collection, plan))
18
+ .filter((collection) => !brokenJunctions.has(collection));
13
19
  }
14
- async function getDataFromCollection(collection, dir) {
20
+ async function getCollectionItems(collection) {
21
+ const items = [];
22
+ let page = 1;
23
+ while (true) {
24
+ // eslint-disable-next-line no-await-in-loop
25
+ const response = (await api.client.request(readItems(collection, { limit: PAGE_SIZE, page })));
26
+ items.push(...response);
27
+ if (response.length < PAGE_SIZE)
28
+ break;
29
+ page++;
30
+ }
31
+ return items;
32
+ }
33
+ function getExcludedRelationFields(collection, relations, plan) {
34
+ if (!plan?.partial || plan.relationStrategy === 'deep')
35
+ return [];
36
+ const m2oFields = relations
37
+ .filter((relation) => relation.collection === collection)
38
+ .filter((relation) => Boolean(relation.related_collection))
39
+ .filter((relation) => !includesCollection(relation.related_collection, plan))
40
+ .map((relation) => ({
41
+ field: relation.field,
42
+ relatedCollection: relation.related_collection,
43
+ type: 'm2o',
44
+ }));
45
+ const aliasFields = relations
46
+ .filter((relation) => relation.related_collection === collection)
47
+ .filter((relation) => Boolean(relation.meta?.one_field))
48
+ .filter((relation) => !includesCollection(relation.collection, plan))
49
+ .map((relation) => ({
50
+ field: relation.meta.one_field,
51
+ relatedCollection: relation.collection,
52
+ type: 'alias',
53
+ }));
54
+ return [...m2oFields, ...aliasFields];
55
+ }
56
+ function hasValue(value) {
57
+ if (Array.isArray(value))
58
+ return value.length > 0;
59
+ return value !== null && value !== undefined;
60
+ }
61
+ function emptyExcludedRelations(items, relations) {
62
+ for (const item of items) {
63
+ for (const relation of relations) {
64
+ if (!(relation.field in item))
65
+ continue;
66
+ if (relation.type === 'alias') {
67
+ delete item[relation.field];
68
+ }
69
+ else {
70
+ item[relation.field] = null;
71
+ }
72
+ }
73
+ }
74
+ }
75
+ function getBrokenRelationWarnings(collection, items, relations) {
76
+ return relations
77
+ .map((relation) => ({
78
+ collection,
79
+ count: items.filter((item) => hasValue(item[relation.field])).length,
80
+ field: relation.field,
81
+ relatedCollection: relation.relatedCollection,
82
+ type: 'excluded_relation',
83
+ }))
84
+ .filter((warning) => warning.count > 0);
85
+ }
86
+ async function getDataFromCollection(collection, dir, relations, plan) {
15
87
  try {
16
- const response = await api.client.request(readItems(collection, { limit: -1 }));
88
+ ux.action.status = `Extracting content: ${collection}`;
89
+ const response = await getCollectionItems(collection);
90
+ const excludedRelations = getExcludedRelationFields(collection, relations, plan);
91
+ if (plan?.relationStrategy === 'empty') {
92
+ emptyExcludedRelations(response, excludedRelations);
93
+ }
94
+ const warnings = plan?.relationStrategy === 'preserve' ? getBrokenRelationWarnings(collection, response, excludedRelations) : [];
17
95
  await writeToFile(`${collection}`, response, `${dir}/content/`);
96
+ return warnings;
18
97
  }
19
98
  catch (error) {
20
- catchError(error);
99
+ catchError(error, {
100
+ context: { collection, function: 'getDataFromCollection' },
101
+ fatal: true,
102
+ });
103
+ throw error;
21
104
  }
22
105
  }
23
- export async function extractContent(dir) {
106
+ export async function extractContent(dir, plan) {
24
107
  ux.action.start(ux.colorize(DIRECTUS_PINK, 'Extracting content'));
108
+ const warnings = [];
25
109
  try {
26
- const collections = await getCollections();
27
- await Promise.all(collections.map(collection => getDataFromCollection(collection, dir)));
110
+ const relations = (await api.client.request(readRelations()));
111
+ const collections = await getCollections(relations, plan);
112
+ for (const collection of collections) {
113
+ // eslint-disable-next-line no-await-in-loop
114
+ const collectionWarnings = await getDataFromCollection(collection, dir, relations, plan);
115
+ warnings.push(...collectionWarnings);
116
+ }
28
117
  }
29
118
  catch (error) {
30
- catchError(error);
119
+ catchError(error, {
120
+ context: { function: 'extractContent' },
121
+ fatal: true,
122
+ });
31
123
  }
32
124
  ux.action.stop();
125
+ return warnings;
33
126
  }
@@ -1,4 +1,5 @@
1
+ import { type TemplatePlan } from '../template-plan/index.js';
1
2
  /**
2
3
  * Extract fields from the Directus instance
3
4
  */
4
- export default function extractFields(dir: string): Promise<void>;
5
+ export default function extractFields(dir: string, plan?: TemplatePlan): Promise<void>;
@@ -2,12 +2,13 @@ import { readFields } from '@directus/sdk';
2
2
  import { ux } from '@oclif/core';
3
3
  import { DIRECTUS_PINK } from '../constants.js';
4
4
  import { api } from '../sdk.js';
5
+ import { includesSchemaCollection } from '../template-plan/index.js';
5
6
  import catchError from '../utils/catch-error.js';
6
7
  import writeToFile from '../utils/write-to-file.js';
7
8
  /**
8
9
  * Extract fields from the Directus instance
9
10
  */
10
- export default async function extractFields(dir) {
11
+ export default async function extractFields(dir, plan) {
11
12
  ux.action.start(ux.colorize(DIRECTUS_PINK, 'Extracting fields'));
12
13
  try {
13
14
  const response = await api.client.request(readFields());
@@ -15,10 +16,9 @@ export default async function extractFields(dir) {
15
16
  throw new TypeError('Unexpected response format');
16
17
  }
17
18
  const fields = response
18
- .filter(
19
- // @ts-ignore
20
- (i) => i.meta && !i.meta.system)
21
- .map(i => {
19
+ .filter((i) => i.meta && !i.meta.system)
20
+ .filter((i) => includesSchemaCollection(i.collection, plan))
21
+ .map((i) => {
22
22
  if (i.meta) {
23
23
  delete i.meta.id;
24
24
  }
@@ -1,4 +1,5 @@
1
+ import { type TemplatePlan } from '../template-plan/index.js';
1
2
  /**
2
3
  * Extract relations from the Directus instance
3
4
  */
4
- export default function extractRelations(dir: string): Promise<void>;
5
+ export default function extractRelations(dir: string, plan?: TemplatePlan): Promise<void>;
@@ -2,12 +2,13 @@ import { readFields, readRelations } from '@directus/sdk';
2
2
  import { ux } from '@oclif/core';
3
3
  import { DIRECTUS_PINK } from '../constants.js';
4
4
  import { api } from '../sdk.js';
5
+ import { includesRelation } from '../template-plan/index.js';
5
6
  import catchError from '../utils/catch-error.js';
6
7
  import writeToFile from '../utils/write-to-file.js';
7
8
  /**
8
9
  * Extract relations from the Directus instance
9
10
  */
10
- export default async function extractRelations(dir) {
11
+ export default async function extractRelations(dir, plan) {
11
12
  ux.action.start(ux.colorize(DIRECTUS_PINK, 'Extracting relations'));
12
13
  try {
13
14
  const response = await api.client.request(readRelations());
@@ -16,9 +17,10 @@ export default async function extractRelations(dir) {
16
17
  const customFields = fields.filter((i) => !i.meta?.system);
17
18
  const relations = response
18
19
  // Filter out relations where the collection starts with 'directus_' && the field is not within the customFields array
19
- .filter((i) => !i.collection.startsWith('directus_', 0)
20
- || customFields.some((f) => f.collection === i.collection && f.field === i.field))
21
- .map(i => {
20
+ .filter((i) => !i.collection.startsWith('directus_', 0) ||
21
+ customFields.some((f) => f.collection === i.collection && f.field === i.field))
22
+ .filter((i) => includesRelation(i.collection, i.related_collection, plan))
23
+ .map((i) => {
22
24
  delete i.meta.id;
23
25
  return i;
24
26
  });
@@ -1 +1,2 @@
1
- export default function extract(dir: string): Promise<{}>;
1
+ import { type TemplatePlan } from '../template-plan/index.js';
2
+ export default function extract(dir: string, plan?: TemplatePlan): Promise<{}>;
@@ -1,5 +1,9 @@
1
1
  import { ux } from '@oclif/core';
2
- import fs from 'node:fs';
2
+ import fs from 'node:fs/promises';
3
+ import { buildTemplatePlan, writeTemplateMetadata, } from '../template-plan/index.js';
4
+ import catchError from '../utils/catch-error.js';
5
+ import { expandDeepPlan } from './expand-deep-plan.js';
6
+ import { expandSchemaPlan } from './expand-schema-plan.js';
3
7
  import extractAccess from './extract-access.js';
4
8
  import { downloadAllFiles } from './extract-assets.js';
5
9
  import extractCollections from './extract-collections.js';
@@ -19,34 +23,68 @@ import extractSchema from './extract-schema.js';
19
23
  import extractSettings from './extract-settings.js';
20
24
  import extractTranslations from './extract-translations.js';
21
25
  import extractUsers from './extract-users.js';
22
- export default async function extract(dir) {
23
- // Get the destination directory for the actual files
26
+ export default async function extract(dir, plan = buildTemplatePlan()) {
24
27
  const destination = `${dir}/src`;
25
- // Check if directory exists, if not, then create it.
26
- if (!fs.existsSync(destination)) {
27
- ux.stdout(`Attempting to create directory at: ${destination}`);
28
- fs.mkdirSync(destination, { recursive: true });
29
- }
30
- await extractSchema(destination);
31
- await extractCollections(destination);
32
- await extractFields(destination);
33
- await extractRelations(destination);
34
- await extractFolders(destination);
35
- await extractFiles(destination);
36
- await extractUsers(destination);
37
- await extractRoles(destination);
38
- await extractPermissions(destination);
39
- await extractPolicies(destination);
40
- await extractAccess(destination);
41
- await extractPresets(destination);
42
- await extractTranslations(destination);
43
- await extractFlows(destination);
44
- await extractOperations(destination);
45
- await extractDashboards(destination);
46
- await extractPanels(destination);
47
- await extractSettings(destination);
48
- await extractExtensions(destination);
49
- await extractContent(destination);
50
- await downloadAllFiles(destination);
28
+ const schemaPlan = await expandSchemaPlan(plan);
29
+ const effectivePlan = await expandDeepPlan(schemaPlan);
30
+ try {
31
+ await fs.mkdir(destination, { recursive: true });
32
+ }
33
+ catch (error) {
34
+ catchError(error, { context: { destination }, fatal: true });
35
+ }
36
+ if (effectivePlan.components.schema) {
37
+ await extractSchema(destination);
38
+ await extractCollections(destination, effectivePlan);
39
+ await extractFields(destination, effectivePlan);
40
+ await extractRelations(destination, effectivePlan);
41
+ }
42
+ if (effectivePlan.components.files) {
43
+ await extractFolders(destination);
44
+ await extractFiles(destination);
45
+ await downloadAllFiles(destination);
46
+ }
47
+ if (effectivePlan.components.users || effectivePlan.components.permissions) {
48
+ await extractRoles(destination);
49
+ await extractPermissions(destination);
50
+ await extractPolicies(destination);
51
+ if (effectivePlan.components.users) {
52
+ await extractUsers(destination);
53
+ }
54
+ await extractAccess(destination);
55
+ }
56
+ if (effectivePlan.components.settings) {
57
+ await extractPresets(destination);
58
+ await extractTranslations(destination);
59
+ await extractSettings(destination);
60
+ }
61
+ if (effectivePlan.components.flows) {
62
+ await extractFlows(destination);
63
+ await extractOperations(destination);
64
+ }
65
+ if (effectivePlan.components.dashboards) {
66
+ await extractDashboards(destination);
67
+ await extractPanels(destination);
68
+ }
69
+ if (effectivePlan.components.extensions) {
70
+ await extractExtensions(destination);
71
+ }
72
+ const warnings = [];
73
+ if (effectivePlan.components.content) {
74
+ const contentWarnings = await extractContent(destination, effectivePlan);
75
+ warnings.push(...contentWarnings);
76
+ }
77
+ for (const warning of warnings) {
78
+ ux.warn(`Excluded relation: ${warning.collection}.${warning.field} -> ${warning.relatedCollection} (${warning.count} records)`);
79
+ }
80
+ try {
81
+ await writeTemplateMetadata(destination, effectivePlan, warnings);
82
+ }
83
+ catch (error) {
84
+ catchError(error, {
85
+ context: { function: 'writeTemplateMetadata' },
86
+ fatal: true,
87
+ });
88
+ }
51
89
  return {};
52
90
  }
@@ -1,16 +1,21 @@
1
1
  export interface ApplyFlags {
2
+ allowBrokenRelations?: boolean;
3
+ collections?: string;
2
4
  content: boolean;
3
5
  dashboards: boolean;
4
6
  directusToken: string;
5
7
  directusUrl: string;
6
8
  disableTelemetry?: boolean;
9
+ excludeCollections?: string;
7
10
  extensions: boolean;
8
11
  files: boolean;
9
12
  flows: boolean;
13
+ noAssets?: boolean;
10
14
  noExit?: boolean;
11
15
  partial: boolean;
12
16
  permissions: boolean;
13
17
  programmatic: boolean;
18
+ relationStrategy?: 'deep' | 'empty' | 'preserve';
14
19
  schema: boolean;
15
20
  settings: boolean;
16
21
  templateLocation: string;
@@ -19,6 +24,4 @@ export interface ApplyFlags {
19
24
  userPassword: string;
20
25
  users?: boolean;
21
26
  }
22
- export declare const loadFlags: readonly ["content", "dashboards", "extensions", "files", "flows", "permissions", "schema", "settings", "users"];
23
27
  export declare function validateProgrammaticFlags(flags: ApplyFlags): ApplyFlags;
24
- export declare function validateInteractiveFlags(flags: ApplyFlags): ApplyFlags;
@@ -1,16 +1,4 @@
1
1
  import { ux } from '@oclif/core';
2
- import catchError from '../utils/catch-error.js';
3
- export const loadFlags = [
4
- 'content',
5
- 'dashboards',
6
- 'extensions',
7
- 'files',
8
- 'flows',
9
- 'permissions',
10
- 'schema',
11
- 'settings',
12
- 'users',
13
- ];
14
2
  export function validateProgrammaticFlags(flags) {
15
3
  const { directusToken, directusUrl, templateLocation, userEmail, userPassword } = flags;
16
4
  if (!directusUrl)
@@ -19,43 +7,5 @@ export function validateProgrammaticFlags(flags) {
19
7
  ux.error('Either Directus token or email and password are required for programmatic mode.');
20
8
  if (!templateLocation)
21
9
  ux.error('Template location is required for programmatic mode.');
22
- return flags.partial ? handlePartialFlags(flags) : setAllFlagsTrue(flags);
23
- }
24
- export function validateInteractiveFlags(flags) {
25
- return flags.partial ? handlePartialFlags(flags) : setAllFlagsTrue(flags);
26
- }
27
- function handlePartialFlags(flags) {
28
- const enabledFlags = loadFlags.filter(flag => flags[flag] === true);
29
- const disabledFlags = loadFlags.filter(flag => flags[flag] === false);
30
- if (enabledFlags.length > 0) {
31
- for (const flag of loadFlags)
32
- flags[flag] = enabledFlags.includes(flag);
33
- }
34
- else if (disabledFlags.length > 0) {
35
- for (const flag of loadFlags)
36
- flags[flag] = !disabledFlags.includes(flag);
37
- }
38
- else {
39
- setAllFlagsTrue(flags);
40
- }
41
- handleDependencies(flags);
42
- if (!loadFlags.some(flag => flags[flag])) {
43
- catchError(new Error('When using --partial, at least one component must be loaded.'), { fatal: true });
44
- }
45
- return flags;
46
- }
47
- function handleDependencies(flags) {
48
- if (flags.content && (!flags.schema || !flags.files)) {
49
- flags.schema = flags.files = true;
50
- ux.warn('Content loading requires schema and files. Enabling schema and files flags.');
51
- }
52
- if (flags.users && !flags.permissions) {
53
- flags.permissions = true;
54
- ux.warn('User loading requires permissions. Enabling permissions flag.');
55
- }
56
- }
57
- function setAllFlagsTrue(flags) {
58
- for (const flag of loadFlags)
59
- flags[flag] = true;
60
10
  return flags;
61
11
  }
@@ -0,0 +1,2 @@
1
+ import { type TemplatePlan } from '../template-plan/index.js';
2
+ export default function finalizeCollections(dir: string, plan?: TemplatePlan): Promise<void>;
@@ -0,0 +1,28 @@
1
+ import { updateCollection } from '@directus/sdk';
2
+ import { ux } from '@oclif/core';
3
+ import { DIRECTUS_PINK } from '../constants.js';
4
+ import { api } from '../sdk.js';
5
+ import { includesSchemaCollection } from '../template-plan/index.js';
6
+ import catchError from '../utils/catch-error.js';
7
+ import readFile from '../utils/read-file.js';
8
+ export default async function finalizeCollections(dir, plan) {
9
+ const collections = readFile('collections', dir)
10
+ .filter((collection) => includesSchemaCollection(collection.collection, plan))
11
+ .filter((collection) => !collection.collection.startsWith('directus_'));
12
+ ux.action.start(ux.colorize(DIRECTUS_PINK, `Finalizing metadata for ${collections.length} collections`));
13
+ const collectionNames = new Set(collections.map((collection) => collection.collection));
14
+ for await (const collection of collections) {
15
+ const meta = { ...collection.meta };
16
+ if (meta.group && !collectionNames.has(meta.group)) {
17
+ ux.warn(`Skipping missing group "${meta.group}" for collection "${collection.collection}"`);
18
+ delete meta.group;
19
+ }
20
+ try {
21
+ await api.client.request(updateCollection(collection.collection, { meta }));
22
+ }
23
+ catch (error) {
24
+ catchError(error, { context: { collection: collection.collection, group: meta.group } });
25
+ }
26
+ }
27
+ ux.action.stop();
28
+ }
@@ -0,0 +1,2 @@
1
+ import { type TemplatePlan } from '../template-plan/index.js';
2
+ export default function finalizeFields(dir: string, plan?: TemplatePlan): Promise<void>;
@@ -0,0 +1,25 @@
1
+ import { updateField } from '@directus/sdk';
2
+ import { ux } from '@oclif/core';
3
+ import { DIRECTUS_PINK } from '../constants.js';
4
+ import { api } from '../sdk.js';
5
+ import { includesSchemaCollection } from '../template-plan/index.js';
6
+ import catchError from '../utils/catch-error.js';
7
+ import readFile from '../utils/read-file.js';
8
+ export default async function finalizeFields(dir, plan) {
9
+ const fields = readFile('fields', dir)
10
+ .filter((field) => includesSchemaCollection(field.collection, plan))
11
+ .filter((field) => field.schema);
12
+ ux.action.start(ux.colorize(DIRECTUS_PINK, `Finalizing metadata for ${fields.length} fields`));
13
+ for await (const field of fields) {
14
+ try {
15
+ await api.client.request(updateField(field.collection, field.field, {
16
+ meta: field.meta ? { ...field.meta } : undefined,
17
+ schema: field.schema ? { ...field.schema } : undefined,
18
+ }));
19
+ }
20
+ catch (error) {
21
+ catchError(error, { context: { collection: field.collection, field: field.field } });
22
+ }
23
+ }
24
+ ux.action.stop();
25
+ }