Skip to content
41 changes: 38 additions & 3 deletions src/commands/extract.ts
Original file line number Diff line number Diff line change
@@ -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'
Expand All @@ -17,7 +17,9 @@ import {
interface ExtractFlags {
directusToken: string;
directusUrl: string;
excludeCollections?: string[];
programmatic: boolean;
skipFiles?: boolean;
templateLocation: string;
templateName: string;
userEmail: string;
Expand All @@ -30,12 +32,26 @@ 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: Flags.string({
char: 'e',
delimiter: ',', // Will split on commas and return an array
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Delimiter solves programmatic mode

description: 'Comma-separated list of collection names to exclude from extraction',
multiple: true,
required: false,
}),
programmatic: customFlags.programmatic,
skipFiles: Flags.boolean({
char: 'f',
default: false,
description: 'Skip extracting files and assets',
required: false,
}),
templateLocation: customFlags.templateLocation,
templateName: customFlags.templateName,
userEmail: customFlags.userEmail,
Expand Down Expand Up @@ -84,9 +100,16 @@ 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, {
excludeCollections: flags.excludeCollections,
skipFiles: flags.skipFiles,
})

ux.action.stop()

Expand All @@ -111,6 +134,18 @@ export default class ExtractCommand extends Command {

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},
)

if (excludeCollectionsInput) {
flags.excludeCollections = excludeCollectionsInput.split(',').map(name => name.trim())
}

const skipFiles = await ux.confirm('Skip extracting files and assets? (y/N)')
flags.skipFiles = skipFiles

ux.log(SEPARATOR)

// Get Directus URL
Expand Down
16 changes: 11 additions & 5 deletions src/lib/extract/extract-content.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 => item.schema !== null)
.filter(item => !excludeCollections?.includes(item.collection))
.map(i => i.collection)
}

Expand All @@ -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)
Expand Down
23 changes: 18 additions & 5 deletions src/lib/extract/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
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'

Expand All @@ -37,8 +44,11 @@ export default async function extract(dir: 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)
Expand All @@ -59,9 +69,12 @@ export default async function extract(dir: string) {
await extractSettings(destination)
await extractExtensions(destination)

await extractContent(destination)
await extractContent(destination, excludeCollections)

await downloadAllFiles(destination)
// Only download files if skipFiles is false
if (!skipFiles) {
await downloadAllFiles(destination)
}

return {}
}