diff --git a/README.md b/README.md index 0c3528d..df3cd16 100644 --- a/README.md +++ b/README.md @@ -155,26 +155,29 @@ These options can be used with any command to configure the operation of `direct - `--collections-path ` Specify the path for the collections dump, relative to the dump path. The default is `"collections"`. +- `-o, --only-collections ` + Comma-separated list of directus collections to include during `pull` `push` or `diff` process. + +- `-x, --exclude-collections ` + Comma-separated list of directus collections to exclude during `pull` `push` or `diff`. Can be used along with `only-collections`. + - `--snapshot-path ` Specify the path for the schema snapshot dump, relative to the dump path. The default is `"snapshot"`. +- `--no-snapshot` + Do not pull and push the Directus schema. By default, the schema is pulled and pushed. + - `--no-split` Indicates whether the schema snapshot should be split into multiple files. By default, snapshots are split. -- `-f, --force` - Force the diff of schema, even if the Directus version is different. The default is `false`. - - `--specs-path ` Specify the path for the specifications dump (GraphQL & OpenAPI), relative to the dump path. The default is `"specs"`. - `--no-specs` Do not dump the specifications (GraphQL & OpenAPI). By default, specifications are dumped. -- `-o, --only-collections ` - Comma-separated list of directus collections to include during `pull` `push` or `diff` process. - -- `-x, --exclude-collections ` - Comma-separated list of directus collections to exclude during `pull` `push` or `diff`. Can be used along with `only-collections`. +- `-f, --force` + Force the diff of schema, even if the Directus version is different. The default is `false`. - `-h, --help` Display help information for the `directus-sync` commands. @@ -201,14 +204,15 @@ module.exports = { directusToken: 'my-directus-token', directusEmail: 'admin@example.com', // ignored if directusToken is provided directusPassword: 'my-directus-password', // ignored if directusToken is provided - split: true, dumpPath: './directus-config', collectionsPath: 'collections', + onlyCollections: ['roles', 'permissions', 'settings'], + excludeCollections: ['settings'], snapshotPath: 'snapshot', + snapshot: true, + split: true, specsPath: 'specs', specs: true, - onlyCollections: ['roles', 'permissions', 'settings'], - excludeCollections: ['settings'], }; ``` diff --git a/packages/cli/src/lib/commands/diff.ts b/packages/cli/src/lib/commands/diff.ts index 0300e1d..cc248c7 100644 --- a/packages/cli/src/lib/commands/diff.ts +++ b/packages/cli/src/lib/commands/diff.ts @@ -1,12 +1,23 @@ import { Container } from 'typedi'; -import { MigrationClient, SnapshotClient } from '../services'; +import { ConfigService, MigrationClient, SnapshotClient } from '../services'; import { loadCollections } from '../loader'; +import pino from 'pino'; +import { LOGGER } from '../constants'; export async function runDiff() { + const logger: pino.Logger = Container.get(LOGGER); + const config = Container.get(ConfigService); + const snapshotConfig = config.getSnapshotConfig(); + // Clear the cache await Container.get(MigrationClient).clearCache(); + // Snapshot - await Container.get(SnapshotClient).diff(); + if (snapshotConfig.enabled) { + await Container.get(SnapshotClient).diff(); + } else { + logger.debug('Snapshot is disabled'); + } // Collections const collections = loadCollections(); diff --git a/packages/cli/src/lib/commands/pull.ts b/packages/cli/src/lib/commands/pull.ts index da1afd7..70afb68 100644 --- a/packages/cli/src/lib/commands/pull.ts +++ b/packages/cli/src/lib/commands/pull.ts @@ -1,17 +1,28 @@ import { Container } from 'typedi'; import { + ConfigService, MigrationClient, SnapshotClient, SpecificationsClient, } from '../services'; import { loadCollections } from '../loader'; +import pino from 'pino'; +import { LOGGER } from '../constants'; export async function runPull() { + const logger: pino.Logger = Container.get(LOGGER); + const config = Container.get(ConfigService); + const snapshotConfig = config.getSnapshotConfig(); + // Clear the cache await Container.get(MigrationClient).clearCache(); // Snapshot - await Container.get(SnapshotClient).pull(); + if (snapshotConfig.enabled) { + await Container.get(SnapshotClient).pull(); + } else { + logger.debug('Snapshot is disabled'); + } // Specifications await Container.get(SpecificationsClient).pull(); diff --git a/packages/cli/src/lib/commands/push.ts b/packages/cli/src/lib/commands/push.ts index 2fee9d0..f793664 100644 --- a/packages/cli/src/lib/commands/push.ts +++ b/packages/cli/src/lib/commands/push.ts @@ -1,18 +1,24 @@ import { Container } from 'typedi'; import pino from 'pino'; -import { MigrationClient, SnapshotClient } from '../services'; +import { ConfigService, MigrationClient, SnapshotClient } from '../services'; import { loadCollections } from '../loader'; import { LOGGER } from '../constants'; export async function runPush() { const logger: pino.Logger = Container.get(LOGGER); + const config = Container.get(ConfigService); + const snapshotConfig = config.getSnapshotConfig(); // Clear the cache await Container.get(MigrationClient).clearCache(); // Snapshot - logger.info(`---- Push schema ----`); - await Container.get(SnapshotClient).push(); + if (snapshotConfig.enabled) { + logger.info(`---- Push schema ----`); + await Container.get(SnapshotClient).push(); + } else { + logger.debug('Snapshot is disabled'); + } // Collections const collections = loadCollections(); diff --git a/packages/cli/src/lib/loader.ts b/packages/cli/src/lib/loader.ts index 87b6a3c..2b9bf71 100644 --- a/packages/cli/src/lib/loader.ts +++ b/packages/cli/src/lib/loader.ts @@ -1,6 +1,7 @@ import { DictionaryValues } from 'ts-essentials'; import { CollectionRecord, + CollectionsList, ConfigService, DashboardsCollection, FlowsCollection, @@ -18,6 +19,7 @@ import { createDumpFolders, getPinoTransport } from './helpers'; import { Container, Token } from 'typedi'; import Logger from 'pino'; import { LOGGER } from './constants'; +import pino from 'pino'; // eslint-disable-next-line @typescript-eslint/require-await export async function initContext( @@ -74,7 +76,11 @@ export function loadCollections() { // Get the collections to process const config = Container.get(ConfigService); + const logger: pino.Logger = Container.get(LOGGER); const collectionsToProcess = config.getCollectionsToProcess(); + const excludedCollections = CollectionsList.filter( + (collection) => !collectionsToProcess.includes(collection), + ); // Initialize the collections const output: CollectionInstance[] = []; @@ -89,5 +95,9 @@ export function loadCollections() { ); } + if (excludedCollections.length > 0) { + logger.debug(`Excluded collections: ${excludedCollections.join(', ')}`); + } + return output; } diff --git a/packages/cli/src/lib/program.ts b/packages/cli/src/lib/program.ts index c30cac0..b3f8529 100644 --- a/packages/cli/src/lib/program.ts +++ b/packages/cli/src/lib/program.ts @@ -23,6 +23,9 @@ function cleanProgramOptions(programOptions: Record) { * Remove some default values from the command options that overrides the config file */ function cleanCommandOptions(commandOptions: Record) { + if (commandOptions.snapshot === true) { + delete commandOptions.snapshot; + } if (commandOptions.split === true) { delete commandOptions.split; } @@ -92,26 +95,37 @@ export function createProgram() { ); // Shared options - const noSplitOption = new Option( - '--no-split', - `should split the schema snapshot into multiple files (default "${DefaultConfig.split}")`, - ); const dumpPathOption = new Option( '--dump-path ', `the base path for the dump (default "${DefaultConfig.dumpPath}")`, ); + const collectionsPathOption = new Option( '--collections-path ', `the path for the collections dump, relative to the dump path (default "${DefaultConfig.collectionsPath}")`, ); + const excludeCollectionsOption = new Option( + '-x, --exclude-collections ', + `comma separated list of collections to exclude from the process (default to none)`, + ).argParser(commaSeparatedList); + const onlyCollectionsOption = new Option( + '-o, --only-collections ', + `comma separated list of collections to include in the process (default to all)`, + ).argParser(commaSeparatedList); + const snapshotPathOption = new Option( '--snapshot-path ', `the path for the schema snapshot dump, relative to the dump path (default "${DefaultConfig.snapshotPath}")`, ); - const forceOption = new Option( - '-f, --force', - `force the diff of schema, even if the Directus version is different (default "${DefaultConfig.force}")`, + const noSnapshotOption = new Option( + '--no-snapshot', + `should pull and push the Directus schema (default "${DefaultConfig.snapshot}")`, + ); + const noSplitOption = new Option( + '--no-split', + `should split the schema snapshot into multiple files (default "${DefaultConfig.split}")`, ); + const specificationsPathOption = new Option( '--specs-path ', `the path for the specifications dump (GraphQL & OpenAPI), relative to the dump path (default "${DefaultConfig.specsPath}")`, @@ -120,14 +134,11 @@ export function createProgram() { '--no-specs', `should dump the GraphQL & OpenAPI specifications (default "${DefaultConfig.specs}")`, ); - const excludeCollectionsOption = new Option( - '-x, --exclude-collections ', - `comma separated list of collections to exclude from the process`, - ).argParser(commaSeparatedList); - const onlyCollectionsOption = new Option( - '-o, --only-collections ', - `comma separated list of collections to include in the process`, - ).argParser(commaSeparatedList); + + const forceOption = new Option( + '-f, --force', + `force the diff of schema, even if the Directus version is different (default "${DefaultConfig.force}")`, + ); program .version(getVersion()) @@ -141,14 +152,15 @@ export function createProgram() { program .command('pull') .description('get the schema and collections and store them locally') - .addOption(noSplitOption) .addOption(dumpPathOption) .addOption(collectionsPathOption) - .addOption(snapshotPathOption) - .addOption(noSpecificationsOption) - .addOption(specificationsPathOption) .addOption(excludeCollectionsOption) .addOption(onlyCollectionsOption) + .addOption(snapshotPathOption) + .addOption(noSnapshotOption) + .addOption(noSplitOption) + .addOption(specificationsPathOption) + .addOption(noSpecificationsOption) .action(wrapAction(program, runPull)); program @@ -156,25 +168,27 @@ export function createProgram() { .description( 'describe the schema and collections diff. Does not modify the database.', ) - .addOption(noSplitOption) .addOption(dumpPathOption) .addOption(collectionsPathOption) - .addOption(snapshotPathOption) - .addOption(forceOption) .addOption(excludeCollectionsOption) .addOption(onlyCollectionsOption) + .addOption(snapshotPathOption) + .addOption(noSnapshotOption) + .addOption(noSplitOption) + .addOption(forceOption) .action(wrapAction(program, runDiff)); program .command('push') .description('push the schema and collections') - .addOption(noSplitOption) .addOption(dumpPathOption) .addOption(collectionsPathOption) - .addOption(snapshotPathOption) - .addOption(forceOption) .addOption(excludeCollectionsOption) .addOption(onlyCollectionsOption) + .addOption(snapshotPathOption) + .addOption(noSnapshotOption) + .addOption(noSplitOption) + .addOption(forceOption) .action(wrapAction(program, runPush)); program diff --git a/packages/cli/src/lib/services/config/config.ts b/packages/cli/src/lib/services/config/config.ts index 6f8e849..9abafed 100644 --- a/packages/cli/src/lib/services/config/config.ts +++ b/packages/cli/src/lib/services/config/config.ts @@ -56,6 +56,7 @@ export class ConfigService { dumpPath: snapshotPath, splitFiles: this.requireOptions('split'), force: this.requireOptions('force'), + enabled: this.requireOptions('snapshot'), }; } diff --git a/packages/cli/src/lib/services/config/default-config.ts b/packages/cli/src/lib/services/config/default-config.ts index 0e9e2ce..e3a999e 100644 --- a/packages/cli/src/lib/services/config/default-config.ts +++ b/packages/cli/src/lib/services/config/default-config.ts @@ -11,27 +11,30 @@ export const DefaultConfig: Pick< | 'debug' | 'dumpPath' | 'collectionsPath' + | 'excludeCollections' + | 'onlyCollections' | 'snapshotPath' + | 'snapshot' | 'split' - | 'force' - | 'specs' | 'specsPath' - | 'excludeCollections' - | 'onlyCollections' + | 'specs' + | 'force' > = { // Global debug: false, // Pull, diff, push dumpPath: './directus-config', + // Collections collectionsPath: 'collections', + excludeCollections: [], + onlyCollections: [], + // Snapshot snapshotPath: 'snapshot', + snapshot: true, split: true, // Specifications - specs: true, specsPath: 'specs', + specs: true, // Diff, push force: false, - // Exclusion and Inclusion - excludeCollections: [], - onlyCollections: [], }; diff --git a/packages/cli/src/lib/services/config/schema.ts b/packages/cli/src/lib/services/config/schema.ts index f9e4f5a..66a339f 100644 --- a/packages/cli/src/lib/services/config/schema.ts +++ b/packages/cli/src/lib/services/config/schema.ts @@ -45,13 +45,18 @@ export const OptionsFields = { directusEmail: z.string().optional(), directusPassword: z.string().optional(), // Pull, diff, push - split: z.boolean(), dumpPath: z.string(), + // Collections collectionsPath: z.string(), + excludeCollections: z.array(CollectionEnum).optional(), + onlyCollections: z.array(CollectionEnum).optional(), + // Snapshot snapshotPath: z.string(), + snapshot: z.boolean(), + split: z.boolean(), // Specifications - specs: z.boolean(), specsPath: z.string(), + specs: z.boolean(), // Diff, push force: z.boolean(), // Untrack @@ -59,9 +64,6 @@ 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); @@ -75,16 +77,18 @@ export const ConfigFileOptionsSchema = z.object({ directusEmail: OptionsFields.directusEmail.optional(), directusPassword: OptionsFields.directusPassword.optional(), // Dump config - split: OptionsFields.split.optional(), dumpPath: OptionsFields.dumpPath.optional(), + // Collections config collectionsPath: OptionsFields.collectionsPath.optional(), + excludeCollections: OptionsFields.excludeCollections.optional(), + onlyCollections: OptionsFields.onlyCollections.optional(), + // Snapshot config snapshotPath: OptionsFields.snapshotPath.optional(), + snapshot: OptionsFields.snapshot.optional(), + split: OptionsFields.split.optional(), // Specifications config - specs: OptionsFields.specs.optional(), specsPath: OptionsFields.specsPath.optional(), + specs: OptionsFields.specs.optional(), // Hooks config hooks: OptionsHooksSchema.optional(), - // Exclusion and Inclusion - excludeCollections: OptionsFields.excludeCollections.optional(), - onlyCollections: OptionsFields.onlyCollections.optional(), }); diff --git a/packages/cli/src/lib/services/specifications/specifications-client.ts b/packages/cli/src/lib/services/specifications/specifications-client.ts index 32a105f..dce84ee 100644 --- a/packages/cli/src/lib/services/specifications/specifications-client.ts +++ b/packages/cli/src/lib/services/specifications/specifications-client.ts @@ -30,7 +30,7 @@ export class SpecificationsClient { @Inject(LOGGER) baseLogger: pino.Logger, protected readonly migrationClient: MigrationClient, ) { - this.logger = getChildLogger(baseLogger, 'snapshot'); + this.logger = getChildLogger(baseLogger, 'specifications'); const { dumpPath, enabled } = config.getSpecificationsConfig(); this.dumpPath = dumpPath; this.enabled = enabled; diff --git a/packages/e2e/spec/entrypoint.spec.ts b/packages/e2e/spec/entrypoint.spec.ts index c8da77f..5223987 100644 --- a/packages/e2e/spec/entrypoint.spec.ts +++ b/packages/e2e/spec/entrypoint.spec.ts @@ -22,6 +22,7 @@ import { import { excludeSomeCollections, includeSomeCollections, + noSnapshot, } from './exclude-include/index.js'; jasmine.DEFAULT_TIMEOUT_INTERVAL = 10000; @@ -64,4 +65,5 @@ describe('Pull and check if ids are preserved for some collections', () => { excludeSomeCollections(context); includeSomeCollections(context); + noSnapshot(context); }); diff --git a/packages/e2e/spec/exclude-include/index.ts b/packages/e2e/spec/exclude-include/index.ts index 44bfe5b..cffd51b 100644 --- a/packages/e2e/spec/exclude-include/index.ts +++ b/packages/e2e/spec/exclude-include/index.ts @@ -1,2 +1,3 @@ export * from './exclude-some-collections.js'; export * from './include-some-collections.js'; +export * from './no-snapshot.js'; diff --git a/packages/e2e/spec/exclude-include/no-snapshot.ts b/packages/e2e/spec/exclude-include/no-snapshot.ts new file mode 100644 index 0000000..83777ba --- /dev/null +++ b/packages/e2e/spec/exclude-include/no-snapshot.ts @@ -0,0 +1,60 @@ +import { Context, debug } from '../helpers/index.js'; +import Path from 'path'; +import fs from 'fs-extra'; + +export const noSnapshot = (context: Context) => { + it('should not pull schema from Directus', async () => { + // Init sync client + const sync = await context.getSync('temp/no-snapshot'); + + // -------------------------------------------------------------------- + // Pull the content from Directus + const output = await sync.pull(['--no-snapshot']); + + // -------------------------------------------------------------------- + // Check that the snapshot was ignored + expect(output).toContain(debug('Snapshot is disabled')); + + // -------------------------------------------------------------------- + // Check if the content was dumped correctly + const dumpPath = sync.getDumpPath(); + const snapshotPath = Path.join(dumpPath, 'snapshot'); + // Ensure folder is empty + const files = fs.readdirSync(snapshotPath); + expect(files).toEqual([]); + }); + + it('should not diff schema from Directus', async () => { + // Init sync client + const sync = await context.getSync( + 'sources/one-item-per-collection', + false, + ); + + // -------------------------------------------------------------------- + // Pull the content from Directus + const output = await sync.diff(['--no-snapshot']); + + // -------------------------------------------------------------------- + // Check that the snapshot was ignored + expect(output).toContain(debug('Snapshot is disabled')); + expect(output).not.toContain(debug('[snapshot] No changes to apply')); + }); + + it('should not diff schema from Directus', async () => { + // Init sync client + const sync = await context.getSync( + 'sources/one-item-per-collection', + false, + ); + + // -------------------------------------------------------------------- + // Pull the content from Directus + const output = await sync.push(['--no-snapshot']); + + // -------------------------------------------------------------------- + // Check that the snapshot was ignored + expect(output).toContain(debug('Snapshot is disabled')); + expect(output).not.toContain(debug('[snapshot] No changes to apply')); + }); +};