Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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

Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "directus-template-cli",
"version": "0.5.1",
"version": "0.6.0-beta.2",
"description": "CLI Utility for applying templates to a Directus instance.",
"author": "bryantgillespie @bryantgillespie",
"bin": {
Expand Down
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 {}
}