From b4a26a2ef83aa50f8a5ca7a3e860e47092075afa Mon Sep 17 00:00:00 2001 From: keesvanbemmel Date: Wed, 15 Jan 2025 12:27:10 +0100 Subject: [PATCH 01/12] Adds partial content extration ability --- src/commands/extract.ts | 26 ++++++++++++++++++++++++-- src/lib/extract/extract-content.ts | 14 ++++++++++---- src/lib/extract/index.ts | 4 ++-- 3 files changed, 36 insertions(+), 8 deletions(-) diff --git a/src/commands/extract.ts b/src/commands/extract.ts index bc7f1ac..0293f58 100644 --- a/src/commands/extract.ts +++ b/src/commands/extract.ts @@ -17,6 +17,7 @@ import { interface ExtractFlags { directusToken: string; directusUrl: string; + excludeCollections?: string[]; programmatic: boolean; templateLocation: string; templateName: string; @@ -30,11 +31,19 @@ export default class ExtractCommand extends Command { static examples = [ '$ directus-template-cli extract', '$ directus-template-cli extract -p --templateName="My Template" --templateLocation="./my-template" --directusToken="admin-token-here" --directusUrl="http://localhost:8055"', + '$ directus-template-cli extract -p --templateName="My Template" --templateLocation="./my-template" --directusToken="admin-token-here" --directusUrl="http://localhost:8055" --excludeCollections=collection1,collection2', ] static flags = { directusToken: customFlags.directusToken, directusUrl: customFlags.directusUrl, + excludeCollections: { + char: 'e', + description: 'Comma-separated list of collection names to exclude from extraction', + multiple: true, + required: false, + type: 'string', + }, programmatic: customFlags.programmatic, templateLocation: customFlags.templateLocation, templateName: customFlags.templateName, @@ -84,9 +93,13 @@ export default class ExtractCommand extends Command { ux.log(SEPARATOR) - ux.action.start(`Extracting template - ${ux.colorize(DIRECTUS_PINK, templateName)} from ${ux.colorize(DIRECTUS_PINK, flags.directusUrl)} to ${ux.colorize(DIRECTUS_PINK, directory)}`) + const exclusionMessage = flags.excludeCollections?.length + ? ` (excluding ${flags.excludeCollections.join(', ')})` + : '' - await extract(directory) + ux.action.start(`Extracting template - ${ux.colorize(DIRECTUS_PINK, templateName)}${exclusionMessage} from ${ux.colorize(DIRECTUS_PINK, flags.directusUrl)} to ${ux.colorize(DIRECTUS_PINK, directory)}`) + + await extract(directory, flags.excludeCollections) ux.action.stop() @@ -109,6 +122,15 @@ export default class ExtractCommand extends Command { {default: `templates/${slugify(templateName, {lower: true, strict: true})}`}, ) + const excludeCollectionsInput = await ux.prompt( + 'Enter collection names to exclude (comma-separated) or press enter to skip:', + {required: false}, + ) + + if (excludeCollectionsInput) { + flags.excludeCollections = excludeCollectionsInput.split(',').map(name => name.trim()) + } + ux.log(`You selected ${ux.colorize(DIRECTUS_PINK, directory)}`) ux.log(SEPARATOR) diff --git a/src/lib/extract/extract-content.ts b/src/lib/extract/extract-content.ts index a2eb864..c37f4fe 100644 --- a/src/lib/extract/extract-content.ts +++ b/src/lib/extract/extract-content.ts @@ -6,11 +6,12 @@ import {api} from '../sdk' import catchError from '../utils/catch-error' import writeToFile from '../utils/write-to-file' -async function getCollections() { +async function getCollections(excludeCollections?: string[]) { const response = await api.client.request(readCollections()) return response .filter(item => !item.collection.startsWith('directus_', 0)) .filter(item => item.schema != null) + .filter(item => !excludeCollections?.includes(item.collection)) .map(i => i.collection) } @@ -23,10 +24,15 @@ async function getDataFromCollection(collection: string, dir: string) { } } -export async function extractContent(dir: string) { - ux.action.start(ux.colorize(DIRECTUS_PINK, 'Extracting content')) +export async function extractContent(dir: string, excludeCollections?: string[]) { + const exclusionMessage = excludeCollections?.length + ? ` (excluding ${excludeCollections.join(', ')})` + : '' + + ux.action.start(ux.colorize(DIRECTUS_PINK, `Extracting content${exclusionMessage}`)) + try { - const collections = await getCollections() + const collections = await getCollections(excludeCollections) await Promise.all(collections.map(collection => getDataFromCollection(collection, dir))) } catch (error) { catchError(error) diff --git a/src/lib/extract/index.ts b/src/lib/extract/index.ts index 9cc785b..accf2cd 100644 --- a/src/lib/extract/index.ts +++ b/src/lib/extract/index.ts @@ -21,7 +21,7 @@ import extractSettings from './extract-settings' import extractTranslations from './extract-translations' import extractUsers from './extract-users' -export default async function extract(dir: string) { +export default async function extract(dir: string, excludeCollections?: string[]) { // Get the destination directory for the actual files const destination = dir + '/src' @@ -59,7 +59,7 @@ export default async function extract(dir: string) { await extractSettings(destination) await extractExtensions(destination) - await extractContent(destination) + await extractContent(destination, excludeCollections) await downloadAllFiles(destination) From 679b2a2e191348026113c731b92a40bbdc457f41 Mon Sep 17 00:00:00 2001 From: keesvanbemmel Date: Wed, 15 Jan 2025 13:34:36 +0100 Subject: [PATCH 02/12] Fixes typescript error --- src/commands/extract.ts | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/commands/extract.ts b/src/commands/extract.ts index 0293f58..9b95914 100644 --- a/src/commands/extract.ts +++ b/src/commands/extract.ts @@ -1,4 +1,4 @@ -import {Command, ux} from '@oclif/core' +import {Command, Flags, ux} from '@oclif/core' import inquirer from 'inquirer' import fs from 'node:fs' import path from 'node:path' @@ -37,13 +37,12 @@ export default class ExtractCommand extends Command { static flags = { directusToken: customFlags.directusToken, directusUrl: customFlags.directusUrl, - excludeCollections: { + excludeCollections: Flags.string({ char: 'e', description: 'Comma-separated list of collection names to exclude from extraction', multiple: true, - required: false, - type: 'string', - }, + required: false + }), programmatic: customFlags.programmatic, templateLocation: customFlags.templateLocation, templateName: customFlags.templateName, From dbbed4f44a0525880b53904b2a7ca19115518ec0 Mon Sep 17 00:00:00 2001 From: keesvanbemmel Date: Wed, 15 Jan 2025 14:19:23 +0100 Subject: [PATCH 03/12] Typo --- src/commands/extract.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/commands/extract.ts b/src/commands/extract.ts index 9b95914..b2c6e14 100644 --- a/src/commands/extract.ts +++ b/src/commands/extract.ts @@ -122,7 +122,7 @@ export default class ExtractCommand extends Command { ) const excludeCollectionsInput = await ux.prompt( - 'Enter collection names to exclude (comma-separated) or press enter to skip:', + 'Enter collection names to exclude (comma-separated) or press enter to skip', {required: false}, ) From eec44820ba896f56d831cd0cba76a068e0489840 Mon Sep 17 00:00:00 2001 From: keesvanbemmel Date: Wed, 15 Jan 2025 14:28:07 +0100 Subject: [PATCH 04/12] Added ability to skip files. --- src/commands/extract.ts | 13 +++++++++++-- src/lib/extract/index.ts | 23 ++++++++++++++++++----- 2 files changed, 29 insertions(+), 7 deletions(-) diff --git a/src/commands/extract.ts b/src/commands/extract.ts index b2c6e14..ffac81d 100644 --- a/src/commands/extract.ts +++ b/src/commands/extract.ts @@ -42,7 +42,13 @@ export default class ExtractCommand extends Command { description: 'Comma-separated list of collection names to exclude from extraction', multiple: true, required: false - }), + }), + skipFiles: Flags.boolean({ + char: 'f', + description: 'Skip extracting files and assets', + required: false, + default: false + }), programmatic: customFlags.programmatic, templateLocation: customFlags.templateLocation, templateName: customFlags.templateName, @@ -98,7 +104,10 @@ export default class ExtractCommand extends Command { ux.action.start(`Extracting template - ${ux.colorize(DIRECTUS_PINK, templateName)}${exclusionMessage} from ${ux.colorize(DIRECTUS_PINK, flags.directusUrl)} to ${ux.colorize(DIRECTUS_PINK, directory)}`) - await extract(directory, flags.excludeCollections) + await extract(directory, { + excludeCollections: flags.excludeCollections, + skipFiles: flags.skipFiles + }) ux.action.stop() diff --git a/src/lib/extract/index.ts b/src/lib/extract/index.ts index accf2cd..d8189f3 100644 --- a/src/lib/extract/index.ts +++ b/src/lib/extract/index.ts @@ -21,7 +21,14 @@ import extractSettings from './extract-settings' import extractTranslations from './extract-translations' import extractUsers from './extract-users' -export default async function extract(dir: string, excludeCollections?: string[]) { +interface ExtractOptions { + excludeCollections?: string[]; + skipFiles?: boolean; +} + +export default async function extract(dir: string, options: ExtractOptions = {}) { + const { excludeCollections, skipFiles = false } = options; + // Get the destination directory for the actual files const destination = dir + '/src' @@ -37,8 +44,11 @@ export default async function extract(dir: string, excludeCollections?: string[] await extractFields(destination) await extractRelations(destination) - await extractFolders(destination) - await extractFiles(destination) + // Only extract files and folders if skipFiles is false + if (!skipFiles) { + await extractFolders(destination) + await extractFiles(destination) + } await extractUsers(destination) await extractRoles(destination) @@ -61,7 +71,10 @@ export default async function extract(dir: string, excludeCollections?: string[] await extractContent(destination, excludeCollections) - await downloadAllFiles(destination) + // Only download files if skipFiles is false + if (!skipFiles) { + await downloadAllFiles(destination) + } return {} -} +} \ No newline at end of file From 408530d46dedfc49d06f10133846b299f65089a4 Mon Sep 17 00:00:00 2001 From: keesvanbemmel Date: Wed, 15 Jan 2025 14:29:28 +0100 Subject: [PATCH 05/12] Missed lines --- src/commands/extract.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/commands/extract.ts b/src/commands/extract.ts index ffac81d..28d435c 100644 --- a/src/commands/extract.ts +++ b/src/commands/extract.ts @@ -18,6 +18,7 @@ interface ExtractFlags { directusToken: string; directusUrl: string; excludeCollections?: string[]; + skipFiles?: boolean; programmatic: boolean; templateLocation: string; templateName: string; @@ -105,9 +106,9 @@ export default class ExtractCommand extends Command { ux.action.start(`Extracting template - ${ux.colorize(DIRECTUS_PINK, templateName)}${exclusionMessage} from ${ux.colorize(DIRECTUS_PINK, flags.directusUrl)} to ${ux.colorize(DIRECTUS_PINK, directory)}`) await extract(directory, { - excludeCollections: flags.excludeCollections, - skipFiles: flags.skipFiles - }) + excludeCollections: flags.excludeCollections, + skipFiles: flags.skipFiles + }); ux.action.stop() From 2ce3a5be59a9e832ddaf538a6530bcc626d49a05 Mon Sep 17 00:00:00 2001 From: keesvanbemmel Date: Wed, 15 Jan 2025 14:42:04 +0100 Subject: [PATCH 06/12] Added skip file confirmation to interactive mode --- src/commands/extract.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/commands/extract.ts b/src/commands/extract.ts index 28d435c..c9dd6e6 100644 --- a/src/commands/extract.ts +++ b/src/commands/extract.ts @@ -131,6 +131,8 @@ export default class ExtractCommand extends Command { {default: `templates/${slugify(templateName, {lower: true, strict: true})}`}, ) + ux.log(`You selected ${ux.colorize(DIRECTUS_PINK, directory)}`) + const excludeCollectionsInput = await ux.prompt( 'Enter collection names to exclude (comma-separated) or press enter to skip', {required: false}, @@ -140,7 +142,8 @@ export default class ExtractCommand extends Command { flags.excludeCollections = excludeCollectionsInput.split(',').map(name => name.trim()) } - ux.log(`You selected ${ux.colorize(DIRECTUS_PINK, directory)}`) + const skipFiles = await ux.confirm('Skip extracting files and assets? (y/N)'); + flags.skipFiles = skipFiles ux.log(SEPARATOR) From d77a67d0000acc1ad5778a5287806689c7ec0b82 Mon Sep 17 00:00:00 2001 From: bryantgillespie Date: Wed, 29 Jan 2025 18:36:06 -0500 Subject: [PATCH 07/12] fix null check --- src/lib/extract/extract-content.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/extract/extract-content.ts b/src/lib/extract/extract-content.ts index c37f4fe..84d60be 100644 --- a/src/lib/extract/extract-content.ts +++ b/src/lib/extract/extract-content.ts @@ -10,7 +10,7 @@ async function getCollections(excludeCollections?: string[]) { const response = await api.client.request(readCollections()) return response .filter(item => !item.collection.startsWith('directus_', 0)) - .filter(item => item.schema != null) + .filter(item => item.schema !== null) .filter(item => !excludeCollections?.includes(item.collection)) .map(i => i.collection) } From ca3e2b322e7bc18dd3a8d5a2499357a98a4f25e2 Mon Sep 17 00:00:00 2001 From: bryantgillespie Date: Wed, 29 Jan 2025 18:37:29 -0500 Subject: [PATCH 08/12] linting and add delimiter prop for flag --- src/commands/extract.ts | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/src/commands/extract.ts b/src/commands/extract.ts index c9dd6e6..a0bbeaa 100644 --- a/src/commands/extract.ts +++ b/src/commands/extract.ts @@ -18,8 +18,8 @@ interface ExtractFlags { directusToken: string; directusUrl: string; excludeCollections?: string[]; - skipFiles?: boolean; programmatic: boolean; + skipFiles?: boolean; templateLocation: string; templateName: string; userEmail: string; @@ -40,17 +40,18 @@ export default class ExtractCommand extends Command { directusUrl: customFlags.directusUrl, excludeCollections: Flags.string({ char: 'e', + delimiter: ',', // Will split on commas and return an array description: 'Comma-separated list of collection names to exclude from extraction', multiple: true, - required: false + required: false, }), + programmatic: customFlags.programmatic, skipFiles: Flags.boolean({ char: 'f', + default: false, description: 'Skip extracting files and assets', required: false, - default: false }), - programmatic: customFlags.programmatic, templateLocation: customFlags.templateLocation, templateName: customFlags.templateName, userEmail: customFlags.userEmail, @@ -106,9 +107,9 @@ export default class ExtractCommand extends Command { ux.action.start(`Extracting template - ${ux.colorize(DIRECTUS_PINK, templateName)}${exclusionMessage} from ${ux.colorize(DIRECTUS_PINK, flags.directusUrl)} to ${ux.colorize(DIRECTUS_PINK, directory)}`) await extract(directory, { - excludeCollections: flags.excludeCollections, - skipFiles: flags.skipFiles - }); + excludeCollections: flags.excludeCollections, + skipFiles: flags.skipFiles, + }) ux.action.stop() @@ -142,7 +143,7 @@ export default class ExtractCommand extends Command { flags.excludeCollections = excludeCollectionsInput.split(',').map(name => name.trim()) } - const skipFiles = await ux.confirm('Skip extracting files and assets? (y/N)'); + const skipFiles = await ux.confirm('Skip extracting files and assets? (y/N)') flags.skipFiles = skipFiles ux.log(SEPARATOR) From f594ff26b32f285cf44975bda9a262cb22f727db Mon Sep 17 00:00:00 2001 From: bryantgillespie Date: Wed, 29 Jan 2025 18:37:44 -0500 Subject: [PATCH 09/12] formatting --- src/lib/extract/index.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/lib/extract/index.ts b/src/lib/extract/index.ts index d8189f3..bb98613 100644 --- a/src/lib/extract/index.ts +++ b/src/lib/extract/index.ts @@ -27,8 +27,8 @@ interface ExtractOptions { } export default async function extract(dir: string, options: ExtractOptions = {}) { - const { excludeCollections, skipFiles = false } = options; - + const {excludeCollections, skipFiles = false} = options + // Get the destination directory for the actual files const destination = dir + '/src' @@ -77,4 +77,4 @@ export default async function extract(dir: string, options: ExtractOptions = {}) } return {} -} \ No newline at end of file +} From e950a846c40bc8fe8a5c9fa67657a986e4b139bc Mon Sep 17 00:00:00 2001 From: bryantgillespie Date: Wed, 29 Jan 2025 18:42:49 -0500 Subject: [PATCH 10/12] 0.6.0-beta.1 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 6df648a..54e57bb 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "directus-template-cli", - "version": "0.5.1", + "version": "0.6.0-beta.1", "description": "CLI Utility for applying templates to a Directus instance.", "author": "bryantgillespie @bryantgillespie", "bin": { From d152ebed51ed4216f7c1520b30a2b7b2ac75285a Mon Sep 17 00:00:00 2001 From: bryantgillespie Date: Wed, 29 Jan 2025 18:47:55 -0500 Subject: [PATCH 11/12] update readme --- README.md | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/README.md b/README.md index 6d4341e..65b8f3f 100644 --- a/README.md +++ b/README.md @@ -181,6 +181,18 @@ Using email/password: npx directus-template-cli@latest extract -p --templateName="My Template" --templateLocation="./my-template" --userEmail="admin@example.com" --userPassword="admin" --directusUrl="http://localhost:8055" ``` +Skipping extracting content from sensitive or large collections: + +``` +npx directus-template-cli@latest extract -p --templateName="My Template" --templateLocation="./my-template" --directusToken="admin-token-here" --directusUrl="http://localhost:8055" --excludeCollections="posts,globals" +``` + +Skipping extracting files and assets: + +``` +npx directus-template-cli@latest extract -p --templateName="My Template" --templateLocation="./my-template" --directusToken="admin-token-here" --directusUrl="http://localhost:8055" --skipFiles +``` + Available flags for programmatic mode: - `--directusUrl`: URL of the Directus instance to extract the template from (required) @@ -189,6 +201,8 @@ Available flags for programmatic mode: - `--userPassword`: Password for Directus authentication (required if not using token) - `--templateLocation`: Directory to extract the template to (required) - `--templateName`: Name of the template (required) +- `--excludeCollections`: Comma-separated list of collection names to exclude from extraction +- `--skipFiles`: Skip extracting files and assets #### Using Environment Variables From 80ab454ec39c487863130d51711b6a6db6719658 Mon Sep 17 00:00:00 2001 From: bryantgillespie Date: Wed, 29 Jan 2025 18:48:05 -0500 Subject: [PATCH 12/12] 0.6.0-beta.2 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 54e57bb..4d9c8fc 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "directus-template-cli", - "version": "0.6.0-beta.1", + "version": "0.6.0-beta.2", "description": "CLI Utility for applying templates to a Directus instance.", "author": "bryantgillespie @bryantgillespie", "bin": {