directus-template-cli 0.7.8 → 0.8.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/commands/init.d.ts +4 -0
- package/dist/commands/init.js +90 -87
- 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 +108 -13
- 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/init/index.d.ts +15 -0
- package/dist/lib/init/index.js +29 -13
- 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 +38 -18
- 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 +3 -1
- package/dist/lib/load/load-data.js +47 -34
- 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 +36 -0
- package/dist/lib/template-plan/metadata.d.ts +5 -0
- package/dist/lib/template-plan/metadata.js +42 -0
- package/dist/lib/template-plan/types.d.ts +34 -0
- package/dist/lib/template-plan/types.js +1 -0
- package/oclif.manifest.json +173 -16
- package/package.json +1 -1
- package/dist/lib/load/update-required-fields.d.ts +0 -1
- package/dist/lib/load/update-required-fields.js +0 -20
|
@@ -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
|
-
|
|
21
|
-
.
|
|
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
|
-
|
|
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
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
fs.
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
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
|
}
|
package/dist/lib/init/index.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { type DownloadTemplateResult } from 'giget';
|
|
2
|
+
import { type PackageManager } from 'nypm';
|
|
2
3
|
import type { InitFlags } from '../../commands/init.js';
|
|
3
4
|
export declare function init({ dir, flags }: {
|
|
4
5
|
dir: string;
|
|
@@ -8,3 +9,17 @@ export declare function init({ dir, flags }: {
|
|
|
8
9
|
frontendDir: string;
|
|
9
10
|
template: DownloadTemplateResult;
|
|
10
11
|
}>;
|
|
12
|
+
type DependencyInstaller = (options: {
|
|
13
|
+
cwd: string;
|
|
14
|
+
packageManager?: PackageManager;
|
|
15
|
+
silent: boolean;
|
|
16
|
+
}) => Promise<unknown>;
|
|
17
|
+
type InstallFrontendDependenciesOptions = {
|
|
18
|
+
frontendDir: string;
|
|
19
|
+
install?: DependencyInstaller;
|
|
20
|
+
onSkip?: () => void;
|
|
21
|
+
packageManager: null | PackageManager;
|
|
22
|
+
warn?: (message: string) => void;
|
|
23
|
+
};
|
|
24
|
+
export declare function installFrontendDependencies({ frontendDir, install, onSkip, packageManager, warn, }: InstallFrontendDependenciesOptions): Promise<boolean>;
|
|
25
|
+
export {};
|
package/dist/lib/init/index.js
CHANGED
|
@@ -133,21 +133,18 @@ export async function init({ dir, flags }) {
|
|
|
133
133
|
// Install dependencies if requested
|
|
134
134
|
if (flags.installDeps) {
|
|
135
135
|
const s = spinner();
|
|
136
|
+
let dependenciesInstalled = true;
|
|
136
137
|
s.start('Installing dependencies');
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
});
|
|
144
|
-
}
|
|
138
|
+
if (fs.existsSync(frontendDir)) {
|
|
139
|
+
dependenciesInstalled = await installFrontendDependencies({
|
|
140
|
+
frontendDir,
|
|
141
|
+
onSkip: () => s.stop('Dependency installation skipped'),
|
|
142
|
+
packageManager,
|
|
143
|
+
});
|
|
145
144
|
}
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
throw error;
|
|
145
|
+
if (dependenciesInstalled) {
|
|
146
|
+
s.stop('Dependencies installed!');
|
|
149
147
|
}
|
|
150
|
-
s.stop('Dependencies installed!');
|
|
151
148
|
}
|
|
152
149
|
// Initialize Git repo
|
|
153
150
|
if (flags.gitInit) {
|
|
@@ -165,7 +162,7 @@ export async function init({ dir, flags }) {
|
|
|
165
162
|
: `- Complete the onboarding form at ${pinkText(directusInfo.url || 'http://localhost:8055')} to create your admin account. \n`;
|
|
166
163
|
const frontendText = flags.frontend ? `- To start the frontend, run ${pinkText(`cd ${flags.frontend}`)} and then ${pinkText(`${packageManager?.name} run dev`)}. \n` : '';
|
|
167
164
|
const projectText = `- Navigate to your project directory using ${pinkText(`cd ${relativeDir}`)}. \n`;
|
|
168
|
-
const readmeText = '- Review the
|
|
165
|
+
const readmeText = '- Review the `./README.md` file for more information and next steps.';
|
|
169
166
|
const nextSteps = `${directusText}${directusLoginText}${projectText}${frontendText}${readmeText}`;
|
|
170
167
|
note(nextSteps, 'Next Steps');
|
|
171
168
|
clackLog.warn(BSL_LICENSE_HEADLINE);
|
|
@@ -203,3 +200,22 @@ async function initGit(targetDir) {
|
|
|
203
200
|
});
|
|
204
201
|
}
|
|
205
202
|
}
|
|
203
|
+
export async function installFrontendDependencies({ frontendDir, install = installDependencies, onSkip, packageManager, warn = ux.warn, }) {
|
|
204
|
+
try {
|
|
205
|
+
await install({
|
|
206
|
+
cwd: frontendDir,
|
|
207
|
+
packageManager: packageManager ?? undefined,
|
|
208
|
+
silent: true,
|
|
209
|
+
});
|
|
210
|
+
return true;
|
|
211
|
+
}
|
|
212
|
+
catch {
|
|
213
|
+
onSkip?.();
|
|
214
|
+
warn('Failed to install dependencies');
|
|
215
|
+
if (packageManager?.name === 'pnpm') {
|
|
216
|
+
warn('This starter uses pnpm. From the frontend directory, try running: corepack enable && pnpm install');
|
|
217
|
+
}
|
|
218
|
+
warn('You can install dependencies manually and continue using the generated project.');
|
|
219
|
+
return false;
|
|
220
|
+
}
|
|
221
|
+
}
|
|
@@ -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,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,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
|
+
}
|
package/dist/lib/load/index.js
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import { ux } from '@oclif/core';
|
|
2
|
+
import { applyMetadataToPlan, buildTemplatePlan, readTemplateMetadata } from '../template-plan/index.js';
|
|
2
3
|
import checkTemplate from '../utils/check-template.js';
|
|
4
|
+
import finalizeCollections from './finalize-collections.js';
|
|
5
|
+
import finalizeFields from './finalize-fields.js';
|
|
3
6
|
import loadAccess from './load-access.js';
|
|
4
7
|
import loadCollections from './load-collections.js';
|
|
5
8
|
import loadDashboards from './load-dashboards.js';
|
|
@@ -16,48 +19,65 @@ import loadRoles from './load-roles.js';
|
|
|
16
19
|
import loadSettings from './load-settings.js';
|
|
17
20
|
import loadTranslations from './load-translations.js';
|
|
18
21
|
import loadUsers from './load-users.js';
|
|
19
|
-
import updateRequiredFields from './update-required-fields.js';
|
|
20
22
|
export default async function apply(dir, flags) {
|
|
21
23
|
const source = `${dir}/src`;
|
|
22
|
-
const
|
|
23
|
-
|
|
24
|
-
|
|
24
|
+
const metadata = readTemplateMetadata(source);
|
|
25
|
+
const requestedPlan = buildTemplatePlan(flags);
|
|
26
|
+
const effectivePlan = applyMetadataToPlan(requestedPlan, metadata);
|
|
27
|
+
const { components } = effectivePlan;
|
|
28
|
+
if (!metadata) {
|
|
29
|
+
ux.warn('No template-meta.json found. Treating as a full template — relation integrity check skipped.');
|
|
25
30
|
}
|
|
26
|
-
if (
|
|
27
|
-
|
|
28
|
-
await loadRelations(source);
|
|
31
|
+
else if (metadata.partial) {
|
|
32
|
+
ux.warn('Template metadata indicates this is a partial template.');
|
|
29
33
|
}
|
|
30
|
-
|
|
34
|
+
const brokenRelationWarnings = metadata?.warnings?.filter((warning) => warning.type === 'excluded_relation') || [];
|
|
35
|
+
if (components.content && brokenRelationWarnings.length > 0 && !effectivePlan.allowBrokenRelations) {
|
|
36
|
+
ux.error('This partial template contains excluded relation references. Re-run with --allow-broken-relations to apply anyway.');
|
|
37
|
+
}
|
|
38
|
+
if (!metadata || components.schema) {
|
|
39
|
+
const isTemplateOk = await checkTemplate(source);
|
|
40
|
+
if (!isTemplateOk) {
|
|
41
|
+
ux.error('The template is missing the collections, fields, or relations files. Older templates are not supported in v0.4 of directus-template-cli. Try using v0.3 to load older templates npx directus-template-cli@0.3 apply or extract the template using latest version before applying. Exiting...');
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
if (components.schema) {
|
|
45
|
+
await loadCollections(source, effectivePlan);
|
|
46
|
+
await loadRelations(source, effectivePlan);
|
|
47
|
+
await finalizeCollections(source, effectivePlan);
|
|
48
|
+
}
|
|
49
|
+
if (components.permissions || components.users) {
|
|
31
50
|
await loadRoles(source);
|
|
32
51
|
await loadPolicies(source);
|
|
33
52
|
await loadPermissions(source);
|
|
34
|
-
if (
|
|
53
|
+
if (components.users) {
|
|
35
54
|
await loadUsers(source);
|
|
36
55
|
}
|
|
37
56
|
await loadAccess(source);
|
|
38
57
|
}
|
|
39
|
-
if (
|
|
58
|
+
if (components.files) {
|
|
40
59
|
await loadFolders(source);
|
|
41
60
|
await loadFiles(source);
|
|
42
61
|
}
|
|
43
|
-
if (
|
|
44
|
-
await loadData(source);
|
|
62
|
+
if (components.content) {
|
|
63
|
+
await loadData(source, effectivePlan);
|
|
45
64
|
}
|
|
46
|
-
if (
|
|
47
|
-
|
|
65
|
+
if (components.schema) {
|
|
66
|
+
// Finalize fields after data loading because skeleton records rely on relaxed constraints.
|
|
67
|
+
await finalizeFields(source, effectivePlan);
|
|
48
68
|
}
|
|
49
|
-
if (
|
|
69
|
+
if (components.dashboards) {
|
|
50
70
|
await loadDashboards(source);
|
|
51
71
|
}
|
|
52
|
-
if (
|
|
72
|
+
if (components.flows) {
|
|
53
73
|
await loadFlows(source);
|
|
54
74
|
}
|
|
55
|
-
if (
|
|
75
|
+
if (components.settings) {
|
|
56
76
|
await loadSettings(source);
|
|
57
77
|
await loadTranslations(source);
|
|
58
78
|
await loadPresets(source);
|
|
59
79
|
}
|
|
60
|
-
if (
|
|
80
|
+
if (components.extensions) {
|
|
61
81
|
await loadExtensions(source);
|
|
62
82
|
}
|
|
63
83
|
return {};
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
+
import { type TemplatePlan } from '../template-plan/index.js';
|
|
1
2
|
/**
|
|
2
3
|
* Load collections into the Directus instance
|
|
3
4
|
* @param dir - The directory to read the collections and fields from
|
|
4
5
|
* @returns {Promise<void>} - Returns nothing
|
|
5
6
|
*/
|
|
6
|
-
export default function loadCollections(dir: string): Promise<void>;
|
|
7
|
+
export default function loadCollections(dir: string, plan?: TemplatePlan): Promise<void>;
|
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import { createCollection, createField, readCollections, readFields
|
|
1
|
+
import { createCollection, createField, readCollections, 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 readFile from '../utils/read-file.js';
|
|
7
8
|
/**
|
|
@@ -9,12 +10,11 @@ import readFile from '../utils/read-file.js';
|
|
|
9
10
|
* @param dir - The directory to read the collections and fields from
|
|
10
11
|
* @returns {Promise<void>} - Returns nothing
|
|
11
12
|
*/
|
|
12
|
-
export default async function loadCollections(dir) {
|
|
13
|
-
const collectionsToAdd = readFile('collections', dir);
|
|
14
|
-
const fieldsToAdd = readFile('fields', dir);
|
|
13
|
+
export default async function loadCollections(dir, plan) {
|
|
14
|
+
const collectionsToAdd = readFile('collections', dir).filter((collection) => includesSchemaCollection(collection.collection, plan));
|
|
15
|
+
const fieldsToAdd = readFile('fields', dir).filter((field) => includesSchemaCollection(field.collection, plan));
|
|
15
16
|
ux.action.start(ux.colorize(DIRECTUS_PINK, `Loading ${collectionsToAdd.length} collections and ${fieldsToAdd.length} fields`));
|
|
16
17
|
await processCollections(collectionsToAdd, fieldsToAdd);
|
|
17
|
-
await updateCollections(collectionsToAdd);
|
|
18
18
|
await addCustomFieldsOnSystemCollections(fieldsToAdd);
|
|
19
19
|
ux.action.stop();
|
|
20
20
|
}
|
|
@@ -24,10 +24,12 @@ async function processCollections(collectionsToAdd, fieldsToAdd) {
|
|
|
24
24
|
for await (const collection of collectionsToAdd) {
|
|
25
25
|
try {
|
|
26
26
|
const existingCollection = existingCollections.find((c) => c.collection === collection.collection);
|
|
27
|
-
await (existingCollection
|
|
27
|
+
await (existingCollection
|
|
28
|
+
? addNewFieldsToExistingCollection(collection.collection, fieldsToAdd, existingFields)
|
|
29
|
+
: addNewCollectionWithFields(collection, fieldsToAdd));
|
|
28
30
|
}
|
|
29
31
|
catch (error) {
|
|
30
|
-
catchError(error);
|
|
32
|
+
catchError(error, { context: { collection: collection.collection } });
|
|
31
33
|
}
|
|
32
34
|
}
|
|
33
35
|
}
|
|
@@ -44,8 +46,9 @@ const removeRequiredorIsNullable = (field) => {
|
|
|
44
46
|
return field;
|
|
45
47
|
};
|
|
46
48
|
async function addNewCollectionWithFields(collection, allFields) {
|
|
47
|
-
const collectionFields = allFields
|
|
48
|
-
.
|
|
49
|
+
const collectionFields = allFields
|
|
50
|
+
.filter((field) => field.collection === collection.collection)
|
|
51
|
+
.map((field) => removeRequiredorIsNullable(field));
|
|
49
52
|
const collectionWithoutGroup = {
|
|
50
53
|
...collection,
|
|
51
54
|
fields: collectionFields,
|
|
@@ -55,8 +58,9 @@ async function addNewCollectionWithFields(collection, allFields) {
|
|
|
55
58
|
await api.client.request(createCollection(collectionWithoutGroup));
|
|
56
59
|
}
|
|
57
60
|
async function addNewFieldsToExistingCollection(collectionName, fieldsToAdd, existingFields) {
|
|
58
|
-
const collectionFieldsToAdd = fieldsToAdd
|
|
59
|
-
.
|
|
61
|
+
const collectionFieldsToAdd = fieldsToAdd
|
|
62
|
+
.filter((field) => field.collection === collectionName)
|
|
63
|
+
.map((field) => removeRequiredorIsNullable(field));
|
|
60
64
|
const existingCollectionFields = existingFields.filter((field) => field.collection === collectionName);
|
|
61
65
|
for await (const field of collectionFieldsToAdd) {
|
|
62
66
|
if (!existingCollectionFields.some((existingField) => existingField.field === field.field)) {
|
|
@@ -65,28 +69,11 @@ async function addNewFieldsToExistingCollection(collectionName, fieldsToAdd, exi
|
|
|
65
69
|
await api.client.request(createField(collectionName, field));
|
|
66
70
|
}
|
|
67
71
|
catch (error) {
|
|
68
|
-
catchError(error);
|
|
72
|
+
catchError(error, { context: { collection: collectionName, field: field.field } });
|
|
69
73
|
}
|
|
70
74
|
}
|
|
71
75
|
}
|
|
72
76
|
}
|
|
73
|
-
async function updateCollections(collections) {
|
|
74
|
-
for await (const collection of collections) {
|
|
75
|
-
try {
|
|
76
|
-
if (collection.meta.group) {
|
|
77
|
-
const pl = {
|
|
78
|
-
meta: {
|
|
79
|
-
group: collection.meta.group,
|
|
80
|
-
},
|
|
81
|
-
};
|
|
82
|
-
await api.client.request(updateCollection(collection.collection, pl));
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
catch (error) {
|
|
86
|
-
catchError(error);
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
77
|
async function addCustomFieldsOnSystemCollections(fields) {
|
|
91
78
|
const customFields = fields.filter((field) => field.collection.startsWith('directus_'));
|
|
92
79
|
const existingFields = await api.client.request(readFields());
|
|
@@ -99,7 +86,7 @@ async function addCustomFieldsOnSystemCollections(fields) {
|
|
|
99
86
|
}
|
|
100
87
|
}
|
|
101
88
|
catch (error) {
|
|
102
|
-
catchError(error);
|
|
89
|
+
catchError(error, { context: { collection: field.collection, field: field.field } });
|
|
103
90
|
}
|
|
104
91
|
}
|
|
105
92
|
}
|
|
@@ -1 +1,3 @@
|
|
|
1
|
-
|
|
1
|
+
import { type TemplatePlan } from '../template-plan/index.js';
|
|
2
|
+
export default function loadData(dir: string, plan?: TemplatePlan): Promise<void>;
|
|
3
|
+
export declare function getUserCollections(dir: string, plan?: TemplatePlan): any[];
|