UNPKG

@sprucelabs/spruce-cli

Version:

Command line interface for building Spruce skills.

506 lines (389 loc) • 15.9 kB
import pathUtil from 'path' import { versionUtil } from '@sprucelabs/spruce-skill-utils' import { diskUtil } from '@sprucelabs/spruce-skill-utils' import { CORE_SCHEMA_VERSION, CORE_NAMESPACE, DEFAULT_GLOBAL_SCHEMA_NAMESPACE, } from '@sprucelabs/spruce-skill-utils' import { assert, test } from '@sprucelabs/test-utils' import { errorAssert } from '@sprucelabs/test-utils' import { coreSchemas } from '../../../features/schema/stores/SchemaStore' import AbstractSchemaTest from '../../../tests/AbstractSchemaTest' import testUtil from '../../../tests/utilities/test.utility' //TODO rethink how to check for count of files and results or where it may make sense to move this test const TYPE_FILE_COUNT = 2 // (only fieldMap and field.types... schema.types checked by +1 in tests) const SYNC_FILE_COUNT = Object.keys(coreSchemas).length + TYPE_FILE_COUNT const MOCK_CORE_SYNC_FILE_COUNT = 4 + TYPE_FILE_COUNT export default class KeepsSchemasInSyncTest extends AbstractSchemaTest { private static readonly coreSyncOptions = { shouldGenerateCoreSchemaTypes: true, } @test() protected static async hasSyncSchemaAction() { await this.Cli() assert.isFunction(this.Action('schema', 'sync').execute) } @test() protected static async failsBecauseSchemasIsNotInstalled() { await this.Cli() const err = await assert.doesThrowAsync(() => this.Action('schema', 'sync').execute({}) ) errorAssert.assertError(err, 'FEATURE_NOT_INSTALLED') } @test() protected static async syncingWithNoSchemasSucceeds() { await this.installSchemaFeature('schemas') const results = await this.Action('schema', 'sync').execute({}) assert.isFalsy(results.errors) //TODO determine better way to count files testUtil.assertCountsByAction(results.files, { updated: 0, generated: SYNC_FILE_COUNT, skipped: 0, }) } @test() protected static async syncingWithNoSchemasAndFetchCoreSchemasFalseSucceeds() { await this.installSchemaFeature('schemas') const results = await this.Action('schema', 'sync').execute({ shouldFetchCoreSchemas: false, }) assert.isFalsy(results.errors) testUtil.assertCountsByAction(results.files, { updated: 0, generated: TYPE_FILE_COUNT, skipped: 0, }) } @test() protected static async syncingCleansUpTempFiles() { await this.installSchemaFeature('schemas') const results = await this.Action('schema', 'sync').execute({}) assert.isFalsy(results.errors) const tmpFile = this.resolveHashSprucePath( 'schemas', 'tmp', 'valueType.tmp.ts' ) assert.isFalse(diskUtil.doesFileExist(tmpFile)) } @test() protected static async syncingCoreSchemasGeneratesTypesFile() { await this.installSchemaFeature('schemas') await this.copyMockCoreSchemas() const results = await this.Action('schema', 'sync').execute( this.coreSyncOptions ) assert.isUndefined(results.errors) assert.isTruthy(results.files) assert.isLength(results.files, MOCK_CORE_SYNC_FILE_COUNT) testUtil.assertCountsByAction(results.files, { generated: MOCK_CORE_SYNC_FILE_COUNT, skipped: 0, updated: 0, }) assert.isTrue(diskUtil.doesFileExist(this.coreSchemaTypesFile)) await this.assertTypesFileGeneratesArraySelect(this.coreSchemaTypesFile) } @test() protected static async syncSchemasTwiceSkipsFiles() { await this.syncSchemas('schemas', this.coreSyncOptions) const results = await this.Action('schema', 'sync').execute( this.coreSyncOptions ) assert.isFalsy(results.errors) assert.isAbove(results.files?.length, 0) testUtil.assertCountsByAction(results.files, { generated: 0, skipped: results.files?.length ?? 0, updated: 0, }) } @test() protected static async makeSureSchemaTypesAreVersioned() { await this.installSchemaFeature('schemas') await this.copyMockCoreSchemas() const results = await this.Action('schema', 'sync').execute( this.coreSyncOptions ) assert.isFalsy(results.errors) const typesContents = diskUtil.readFile(this.coreSchemaTypesFile) assert.doesInclude( typesContents, new RegExp( `SpruceSchemas.${CORE_NAMESPACE}.${CORE_SCHEMA_VERSION.constValue}(.*?)interface Person`, 'gis' ) ) } @test() protected static async shouldGenerateCoreSchemaTypesGeneratesValidFiles() { await this.installSchemaFeature('schemas') await this.copyMockCoreSchemas() const results = await this.Action('schema', 'sync').execute( this.coreSyncOptions ) assert.isFalsy(results.errors) assert.isTruthy(results.files) await this.assertValidActionResponseFiles(results) const typesContents = diskUtil.readFile(this.coreSchemaTypesFile) assert.doesNotInclude( typesContents, /@sprucelabs\/spruce-core-schemas/gi ) assert.doesInclude( typesContents, new RegExp( `export declare namespace ${DEFAULT_GLOBAL_SCHEMA_NAMESPACE}.${CORE_NAMESPACE}.${CORE_SCHEMA_VERSION.constValue}`, 'gis' ) ) const orgSchema = testUtil.assertFileByNameInGeneratedFiles( 'organization.schema.ts', results.files ) const locationSchemaContents = diskUtil.readFile(orgSchema) assert.doesInclude(locationSchemaContents, 'SchemaRegistry') } @test() protected static async generateCoreSchemaInCoreSchemasModule() { await this.installSchemaFeature('schemas') const pkg = this.Service('pkg') pkg.set({ path: 'name', value: '@sprucelabs/spruce-core-schemas' }) await this.copyMockCoreSchemas() const results = await this.Action('schema', 'sync').execute({}) assert.isFalsy(results.errors) assert.isTruthy(results.files) const typesContents = diskUtil.readFile(this.coreSchemaTypesFile) assert.doesNotInclude( typesContents, /@sprucelabs\/spruce-core-schemas/gi ) assert.doesInclude( typesContents, new RegExp( `export declare namespace ${DEFAULT_GLOBAL_SCHEMA_NAMESPACE}.${CORE_NAMESPACE}.${CORE_SCHEMA_VERSION.constValue}`, 'gis' ) ) const orgSchema = testUtil.assertFileByNameInGeneratedFiles( 'organization.schema.ts', results.files ) const locationSchemaContents = diskUtil.readFile(orgSchema) assert.doesInclude(locationSchemaContents, 'SchemaRegistry') } @test() protected static async canHandleHyphenSchemaIds() { await this.syncSchemas('schemas') const createResponse = await this.Action('schema', 'create').execute({ nameReadable: 'Test schema', nameCamel: 'testSchema', }) assert.isFalsy(createResponse.errors) const builderPath = testUtil.assertFileByNameInGeneratedFiles( 'testSchema.builder.ts', createResponse.files ) const contents = diskUtil .readFile(builderPath) .replace("id: 'testSchema'", "id: 'test-schema'") diskUtil.writeFile(builderPath, contents) const syncResults = await this.Action('schema', 'sync').execute({}) const testSchema = testUtil.assertFileByNameInGeneratedFiles( 'test-schema.schema.ts', syncResults.files ) const testSchemaContents = diskUtil.readFile(testSchema) assert.doesInclude(testSchemaContents, 'SchemaRegistry') await this.assertValidActionResponseFiles(syncResults) } @test() protected static async coreSchemasPullFromCoreSchemasModuleDuringNormalGeneration() { await this.syncSchemas('schemas') const createResponse = await this.Action('schema', 'create').execute({ nameReadable: 'Test schema', nameCamel: 'testSchema', }) for (const schema of Object.values(coreSchemas)) { const id = schema.id const match = testUtil.assertFileByNameInGeneratedFiles( `${id}.schema.ts`, createResponse.files ) const contents = diskUtil.readFile(match) assert.doesInclude( contents, `export { ${id}Schema as default } from '@sprucelabs/spruce-core-schemas'` ) } } @test() protected static async schemasStayInSyncWhenBuildersAreDeleted() { await this.syncSchemas('schemas') const version = versionUtil.generateVersion() const typeChecker = this.Service('typeChecker') const createAction = this.Action('schema', 'create') const matcher = new RegExp( `SpruceSchemas.TestingSchemas.${version.constValue}(.*?)interface TestSchema`, 'gis' ) assert.isTrue( diskUtil.doesFileExist(pathUtil.dirname(this.schemaTypesFile)) ) const createResponse = await createAction.execute({ nameReadable: 'Test schema', nameCamel: 'testSchema', }) const builderFile = testUtil.assertFileByNameInGeneratedFiles( /testSchema\.builder/, createResponse.files ) // make sure builder is versioned assert.doesInclude(builderFile, version.dirValue) const schemaFile = testUtil.assertFileByNameInGeneratedFiles( /testSchema\.schema/, createResponse.files ) // make sure this path is versioned assert.doesInclude(schemaFile, version.dirValue) assert.doesInclude(schemaFile, '/testingSchemas/') // schema types should be good await typeChecker.check(this.schemaTypesFile) // the types should include our test schema let typesContents = diskUtil.readFile(this.schemaTypesFile) assert.doesInclude(typesContents, matcher) // the schema file should exist assert.isTrue(diskUtil.doesFileExist(schemaFile)) // DELETE builder and make sure we are cleaned up diskUtil.deleteFile(builderFile) // this should cleanup types and schema files await this.Action('schema', 'sync').execute({}) assert.isTrue(diskUtil.doesFileExist(this.schemaTypesFile)) // and the schema should have been deleted assert.isFalse(diskUtil.doesFileExist(schemaFile)) // and the namespace folder should have been deleted const namespaceFolder = this.resolveHashSprucePath( 'schemas', 'testingSChemas' ) assert.isFalse(diskUtil.doesDirExist(namespaceFolder)) } @test() protected static async canSyncSchemasWhenOnlyNodeModuleIsInstalledAfterDecliningToInstallSkill() { await this.FeatureFixture().installCachedFeatures('schemasInNodeModule') const promise = this.Action('schema', 'sync').execute({}) await this.waitForInput() await this.ui.sendInput('n') await this.waitForInput() await this.ui.sendInput('') const results = await promise assert.isFalsy(results.errors) await this.assertValidActionResponseFiles(results) } @test('syncs minArrayLength, importsWhenRemote') protected static async syncsExpectedFields() { await this.syncSchemas('schemas') const schemasDir = this.resolvePath('src', 'schemas') await diskUtil.copyDir( this.resolveTestPath('test_builders'), schemasDir ) const results = await this.Action('schema', 'sync').execute({}) const schema = await this.importSchema(results, 'schemaTwo.schema.ts') assert.isUndefined(schema.fields.phone.minArrayLength) assert.isEqual(schema.fields.favoriteColors.minArrayLength, 3) assert.isEqual(schema.fields.permissions.minArrayLength, 0) } @test() protected static async canChangeSkillNamespace() { await this.syncSchemas('schemas') await this.Action('schema', 'create').execute({ nameReadable: 'Test schema', nameCamel: 'testSchema', }) const beforeFolder = this.resolveHashSprucePath( 'schemas', 'testingSchemas' ) const afterFolder = this.resolveHashSprucePath( 'schemas', 'newNamespace' ) assert.isTrue(diskUtil.doesFileExist(beforeFolder)) assert.isFalse(diskUtil.doesFileExist(afterFolder)) await this.Store('skill').setCurrentSkillsNamespace('new-namespace') await this.Action('schema', 'sync').execute({}) assert.isFalse(diskUtil.doesFileExist(beforeFolder)) assert.isTrue(diskUtil.doesFileExist(afterFolder)) } @test() protected static async generatedSchemasRetainImportsAndTypeSuffix() { await this.installSchemaFeature('schemas') const results = await this.Action('schema', 'create').execute({ nameReadable: 'Test schema', nameCamel: 'testSchema', }) const testSchemaPath = testUtil.assertFileByNameInGeneratedFiles( 'testSchema.builder.ts', results.files ) diskUtil.writeFile( testSchemaPath, `import { buildSchema } from '@sprucelabs/schema' export default buildSchema({ id: 'testSchema', name: 'Test schema', description: '', importsWhenLocal: ['import * as Local from "@sprucelabs/schema"'], importsWhenRemote: ['import * as Remote from "@sprucelabs/schema"'], typeSuffix: '<S extends Record<string, any> = Record<string, any>>', fields: { fieldName1: { type: 'text', label: 'First Field', isRequired: true, }, fieldName2: { type: 'number', label: 'Second Field', isRequired: true, hint: 'A hint', }, }, }) ` ) const syncResults = await this.Action('schema', 'sync').execute({}) const schemaPath = testUtil.assertFileByNameInGeneratedFiles( 'testSchema.schema.ts', syncResults.files ) const imported = await this.Service('import').importDefault(schemaPath) assert.doesInclude(imported, { importsWhenRemote: ['import * as Remote from "@sprucelabs/schema"'], typeSuffix: '<S extends Record<string, any> = Record<string, any>>', }) } private static async importSchema(results: any, filename: string) { const file = testUtil.assertFileByNameInGeneratedFiles( filename, results.files ) const schema = await this.Service('import').importDefault(file) return schema } private static async assertTypesFileGeneratesArraySelect( typesFile: string ) { const contents = diskUtil.readFile(typesFile) assert.doesInclude(contents, `'favoriteColors'?: ("blue" | "red")[]`) } private static async copyMockCoreSchemas() { const source = this.resolveTestPath('mock_core_builders') const destination = this.resolvePath('src', 'schemas') await diskUtil.copyDir(source, destination) } }