Skip to content

Commit

Permalink
feat(cli): add excludeCollections and onlyCollections options (#52)
Browse files Browse the repository at this point in the history
* feat(cli): can exclude or include collections from process

* chore(cli): run prettier

* test(e2e): add excludeSomeCollections

* test(e2e): add tests on push with exclusions

* refactor(e2e): rename folder

* test(e2e): add includeSomeCollections

* fix(cli): change entrypoint

* docs: explain onlyCollections and excludeCollections options
  • Loading branch information
EdouardDem authored Apr 25, 2024
1 parent 45f1a2b commit e5e0451
Show file tree
Hide file tree
Showing 16 changed files with 393 additions and 31 deletions.
8 changes: 8 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,12 @@ These options can be used with any command to configure the operation of `direct
- `--no-specs`
Do not dump the specifications (GraphQL & OpenAPI). By default, specifications are dumped.

- `-o, --only-collections <onlyCollections>`
Comma-separated list of directus collections to include during `pull` `push` or `diff` process.

- `-x, --exclude-collections <excludeCollections>`
Comma-separated list of directus collections to exclude during `pull` `push` or `diff`. Can be used along with `only-collections`.

- `-h, --help`
Display help information for the `directus-sync` commands.

Expand Down Expand Up @@ -201,6 +207,8 @@ module.exports = {
snapshotPath: 'snapshot',
specsPath: 'specs',
specs: true,
onlyCollections: ['roles', 'permissions', 'settings'],
excludeCollections: ['settings'],
};
```

Expand Down
3 changes: 1 addition & 2 deletions packages/cli/bin/index.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,2 @@
#! /usr/bin/env node
const { run } = require('../dist');
void run();
require('../dist/entrypoint');
3 changes: 2 additions & 1 deletion packages/cli/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
},
"scripts": {
"build": "tsc",
"start": "ts-node src/index.ts",
"start": "ts-node src/entrypoint.ts",
"test": "jest",
"prepublishOnly": "cp ../../README.md ./README.md"
},
Expand All @@ -25,6 +25,7 @@
"@types/jest": "^29.5.12",
"@types/node": "20.11.25",
"jest": "^29.7.0",
"ts-essentials": "^9.4.2",
"ts-jest": "^29.1.2",
"ts-node": "10.9.2",
"typescript": "5.4.2"
Expand Down
6 changes: 6 additions & 0 deletions packages/cli/src/entrypoint.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import 'reflect-metadata';
import 'dotenv/config';

import { run } from './run';

void run();
1 change: 0 additions & 1 deletion packages/cli/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import 'reflect-metadata';
import 'dotenv/config';

export * from './lib';
export * from './run';
52 changes: 38 additions & 14 deletions packages/cli/src/lib/loader.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import { DictionaryValues } from 'ts-essentials';
import {
CollectionRecord,
ConfigService,
DashboardsCollection,
FlowsCollection,
Expand All @@ -13,7 +15,7 @@ import {
WebhooksCollection,
} from './services';
import { createDumpFolders, getPinoTransport } from './helpers';
import { Container } from 'typedi';
import { Container, Token } from 'typedi';
import Logger from 'pino';
import { LOGGER } from './constants';

Expand Down Expand Up @@ -53,17 +55,39 @@ export function disposeContext() {
export function loadCollections() {
// The order of the collections is important
// The collections are populated in the same order
return [
Container.get(SettingsCollection),
Container.get(FoldersCollection),
Container.get(TranslationsCollection),
Container.get(WebhooksCollection),
Container.get(FlowsCollection),
Container.get(OperationsCollection),
Container.get(RolesCollection),
Container.get(PermissionsCollection),
Container.get(DashboardsCollection),
Container.get(PanelsCollection),
Container.get(PresetsCollection),
];
const collectionsConstructors = {
settings: SettingsCollection,
folders: FoldersCollection,
translations: TranslationsCollection,
webhooks: WebhooksCollection,
flows: FlowsCollection,
operations: OperationsCollection,
roles: RolesCollection,
permissions: PermissionsCollection,
dashboards: DashboardsCollection,
panels: PanelsCollection,
presets: PresetsCollection,
} as const satisfies CollectionRecord<unknown>;
type CollectionInstance = InstanceType<
DictionaryValues<typeof collectionsConstructors>
>;

// Get the collections to process
const config = Container.get(ConfigService);
const collectionsToProcess = config.getCollectionsToProcess();

// Initialize the collections
const output: CollectionInstance[] = [];
for (const collection of collectionsToProcess) {
const collectionConstructor = collectionsConstructors[collection];
output.push(
Container.get(
collectionConstructor as Token<
InstanceType<typeof collectionConstructor>
>,
),
);
}

return output;
}
21 changes: 21 additions & 0 deletions packages/cli/src/lib/program.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,13 @@ function getVersion(): string {
}
}

/**
* Split a comma separated list
*/
function commaSeparatedList(value: string) {
return value.split(',').map((v) => v.trim());
}

export function createProgram() {
const program = createCommand();
// Global options
Expand Down Expand Up @@ -113,6 +120,14 @@ export function createProgram() {
'--no-specs',
`should dump the GraphQL & OpenAPI specifications (default "${DefaultConfig.specs}")`,
);
const excludeCollectionsOption = new Option(
'-x, --exclude-collections <excludeCollections>',
`comma separated list of collections to exclude from the process`,
).argParser(commaSeparatedList);
const onlyCollectionsOption = new Option(
'-o, --only-collections <onlyCollections>',
`comma separated list of collections to include in the process`,
).argParser(commaSeparatedList);

program
.version(getVersion())
Expand All @@ -132,6 +147,8 @@ export function createProgram() {
.addOption(snapshotPathOption)
.addOption(noSpecificationsOption)
.addOption(specificationsPathOption)
.addOption(excludeCollectionsOption)
.addOption(onlyCollectionsOption)
.action(wrapAction(program, runPull));

program
Expand All @@ -144,6 +161,8 @@ export function createProgram() {
.addOption(collectionsPathOption)
.addOption(snapshotPathOption)
.addOption(forceOption)
.addOption(excludeCollectionsOption)
.addOption(onlyCollectionsOption)
.action(wrapAction(program, runDiff));

program
Expand All @@ -154,6 +173,8 @@ export function createProgram() {
.addOption(collectionsPathOption)
.addOption(snapshotPathOption)
.addOption(forceOption)
.addOption(excludeCollectionsOption)
.addOption(onlyCollectionsOption)
.action(wrapAction(program, runPush));

program
Expand Down
14 changes: 11 additions & 3 deletions packages/cli/src/lib/services/config/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import {
ConfigFileOptions,
DirectusConfigWithCredentials,
DirectusConfigWithToken,
HookCollectionName,
CollectionName,
Hooks,
OptionName,
Options,
Expand All @@ -14,7 +14,7 @@ import { ConfigFileLoader } from './config-file-loader';
import { zodParse } from '../../helpers';
import deepmerge from 'deepmerge';
import { DefaultConfig, DefaultConfigPaths } from './default-config';
import { OptionsSchema } from './schema';
import { CollectionsList, OptionsSchema } from './schema';

@Service()
export class ConfigService {
Expand Down Expand Up @@ -100,14 +100,22 @@ export class ConfigService {
}

@Cacheable()
getHooksConfig(collection: HookCollectionName): Hooks {
getHooksConfig(collection: CollectionName): Hooks {
const hooks = this.getOptions('hooks');
if (!hooks) {
return {};
}
return (hooks[collection] ?? {}) as Hooks;
}

@Cacheable()
getCollectionsToProcess() {
const exclude = this.requireOptions('excludeCollections');
const only = this.requireOptions('onlyCollections');
const list = only.length > 0 ? only : CollectionsList;
return list.filter((collection) => !exclude.includes(collection));
}

protected getOptions<T extends OptionName>(name: T): Options[T] | undefined {
const options = this.flattenOptions();
return options[name];
Expand Down
5 changes: 5 additions & 0 deletions packages/cli/src/lib/services/config/default-config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ export const DefaultConfig: Pick<
| 'force'
| 'specs'
| 'specsPath'
| 'excludeCollections'
| 'onlyCollections'
> = {
// Global
debug: false,
Expand All @@ -29,4 +31,7 @@ export const DefaultConfig: Pick<
specsPath: 'specs',
// Diff, push
force: false,
// Exclusion and Inclusion
excludeCollections: [],
onlyCollections: [],
};
8 changes: 6 additions & 2 deletions packages/cli/src/lib/services/config/interfaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ import type { z } from 'zod';
import type {
ConfigFileOptionsSchema,
OptionsFields,
OptionsHooksSchema,
OptionsSchema,
CollectionEnum,
} from './schema';
import type { MigrationClient } from '../migration-client';
import type { DirectusBaseType, Query } from '../collections';
Expand All @@ -14,7 +14,11 @@ export type Options = z.infer<typeof OptionsSchema>;

export type ConfigFileOptions = z.infer<typeof ConfigFileOptionsSchema>;

export type HookCollectionName = keyof typeof OptionsHooksSchema.shape;
export type CollectionName = z.infer<typeof CollectionEnum>;

export type CollectionRecord<T> = {
[key in CollectionName]: T;
};

export type TransformDataFunction = <T = unknown>(
data: T[],
Expand Down
25 changes: 23 additions & 2 deletions packages/cli/src/lib/services/config/schema.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,27 @@
import { z } from 'zod';

export const CollectionsList = [
'dashboards',
'flows',
'folders',
'operations',
'panels',
'permissions',
'presets',
'roles',
'settings',
'translations',
'webhooks',
] as const;

export const CollectionEnum = z.enum(CollectionsList);

export const HooksSchema = z.object({
onLoad: z.function().optional(),
onDump: z.function().optional(),
onSave: z.function().optional(),
onQuery: z.function().optional(),
});

export const OptionsHooksSchema = z.object({
dashboards: HooksSchema.optional(),
flows: HooksSchema.optional(),
Expand All @@ -19,7 +34,7 @@ export const OptionsHooksSchema = z.object({
settings: HooksSchema.optional(),
translations: HooksSchema.optional(),
webhooks: HooksSchema.optional(),
});
} satisfies { [key in z.infer<typeof CollectionEnum>]: z.Schema });

export const OptionsFields = {
// Global
Expand All @@ -44,6 +59,9 @@ export const OptionsFields = {
id: z.string().optional(),
// Hooks
hooks: OptionsHooksSchema.optional(),
// Exclusion and Inclusion
excludeCollections: z.array(CollectionEnum).optional(),
onlyCollections: z.array(CollectionEnum).optional(),
};
export const OptionsSchema = z.object(OptionsFields);

Expand All @@ -66,4 +84,7 @@ export const ConfigFileOptionsSchema = z.object({
specsPath: OptionsFields.specsPath.optional(),
// Hooks config
hooks: OptionsHooksSchema.optional(),
// Exclusion and Inclusion
excludeCollections: OptionsFields.excludeCollections.optional(),
onlyCollections: OptionsFields.onlyCollections.optional(),
});
7 changes: 7 additions & 0 deletions packages/e2e/spec/entrypoint.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,10 @@ import {
onSave,
onSaveDuplicate,
} from './hooks/index.js';
import {
excludeSomeCollections,
includeSomeCollections,
} from './exclude-include/index.js';

jasmine.DEFAULT_TIMEOUT_INTERVAL = 10000;

Expand Down Expand Up @@ -57,4 +61,7 @@ describe('Pull and check if ids are preserved for some collections', () => {
onSaveDuplicate(context);
onLoad(context);
onQuery(context);

excludeSomeCollections(context);
includeSomeCollections(context);
});
Loading

0 comments on commit e5e0451

Please sign in to comment.