Skip to content

Commit

Permalink
feat(schema): developers can specify manually the path of the .forest…
Browse files Browse the repository at this point in the history
…admin-schema.json file (#698)

co-authored with @julienfouilhe
  • Loading branch information
arnaudbesnier authored May 5, 2021
1 parent db0ea3d commit c27bfb9
Show file tree
Hide file tree
Showing 7 changed files with 117 additions and 73 deletions.
2 changes: 2 additions & 0 deletions src/context/init.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ const AuthenticationService = require('../services/authentication');
const TokenService = require('../services/token');
const OidcConfigurationRetrieverService = require('../services/oidc-configuration-retriever');
const OidcClientManagerService = require('../services/oidc-client-manager');
const ProjectDirectoryFinder = require('../services/project-directory-finder');

function initValue(context) {
context.addValue('forestUrl', process.env.FOREST_URL || 'https://api.forestadmin.com');
Expand Down Expand Up @@ -132,6 +133,7 @@ function initServices(context) {
context.addInstance('forestServerRequester', forestServerRequester);
context.addInstance('schemasGenerator', schemasGenerator);
context.addInstance('baseFilterParser', baseFilterParser);
context.addClass(ProjectDirectoryFinder);
context.addClass(ConfigStore);
context.addClass(PermissionsGetter);
context.addClass(PermissionsChecker);
Expand Down
7 changes: 3 additions & 4 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,12 @@ const HealthCheckRoute = require('./routes/healthcheck');
const Schemas = require('./generators/schemas');
const SchemaSerializer = require('./serializers/schema');
const Integrator = require('./integrations');
const ProjectDirectoryUtils = require('./utils/project-directory');
const { getJWTConfiguration } = require('./config/jwt');
const initAuthenticationRoutes = require('./routes/authentication');

const {
logger,
path,
pathService,
errorHandler,
ipWhitelist,
Expand All @@ -45,13 +45,10 @@ const PUBLIC_ROUTES = [
...initAuthenticationRoutes.PUBLIC_ROUTES,
];

const pathProjectAbsolute = new ProjectDirectoryUtils().getAbsolutePath();

const ENVIRONMENT_DEVELOPMENT = !process.env.NODE_ENV
|| ['dev', 'development'].includes(process.env.NODE_ENV);
const DISABLE_AUTO_SCHEMA_APPLY = process.env.FOREST_DISABLE_AUTO_SCHEMA_APPLY
&& JSON.parse(process.env.FOREST_DISABLE_AUTO_SCHEMA_APPLY);
const pathSchemaFile = `${pathProjectAbsolute}/.forestadmin-schema.json`;

let jwtAuthenticator;
let app = null;
Expand Down Expand Up @@ -131,6 +128,8 @@ function generateAndSendSchema(envSecret) {
let collectionsSent;
let metaSent;

const pathSchemaFile = path.join(configStore.schemaDir, '.forestadmin-schema.json');

if (ENVIRONMENT_DEVELOPMENT) {
const meta = {
database_type: configStore.Implementation.getDatabaseType(),
Expand Down
25 changes: 24 additions & 1 deletion src/services/config-store.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,18 @@
class ConfigStore {
constructor({ logger, fs, path }) {
constructor({
logger,
fs,
path,
projectDirectoryFinder,
}) {
this.Implementation = null;
this.lianaOptions = null;
this.integrator = null;

this.logger = logger;
this.fs = fs;
this.path = path;
this.projectDirectoryFinder = projectDirectoryFinder;
}

get configDir() {
Expand All @@ -20,10 +26,23 @@ class ConfigStore {
return this.path.resolve('.', 'forest');
}

get schemaDir() {
const schemaDir = this.lianaOptions?.schemaDir;
if (schemaDir) {
return this.path.resolve('.', `${schemaDir}`);
}

return this.path.resolve('.', this.projectDirectoryFinder.getAbsolutePath());
}

doesConfigDirExist() {
return this.fs.existsSync(this.configDir);
}

doesSchemaDirExist() {
return this.fs.existsSync(this.schemaDir);
}

validateOptions() {
const options = this.lianaOptions;

Expand Down Expand Up @@ -63,6 +82,10 @@ class ConfigStore {
this.logger.warn(`Your configDir ("${this.configDir}") does not exist. Please make sure it is set correctly.`);
}

if (!this.doesSchemaDirExist()) {
throw new Error(`Your schemaDir ("${this.schemaDir}") does not exist. Please make sure it is set correctly.`);
}

if (options.onlyCrudModule) {
this.logger.warn('onlyCrudModule is not supported anymore. Please remove this option.');
}
Expand Down
42 changes: 42 additions & 0 deletions src/services/project-directory-finder.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
class ProjectDirectoryFinder {
constructor({ path }) {
this.path = path;
this.dirname = __dirname;

// NOTICE: Order does matter as packages install via yarn 2 Plug n Play mode also
// include node_modules in path.
this.PATHS_ROOT_PACKAGES = [
// Yarn 2 Plug n Play mode
this.path.join('.yarn', 'cache'),
this.path.join('.yarn', 'unplugged'),
// Usual Yarn / NPM
'node_modules',
];
}

ensureAbsolutePath(subPathsToProject) {
// NOTICE: on POSIX system, empty path created by previous split is skipped by path.join.
if (!this.path.isAbsolute(this.path.join(...subPathsToProject))) {
return this.path.join(this.path.sep, ...subPathsToProject);
}
return this.path.join(...subPathsToProject);
}

getAbsolutePath() {
for (let index = 0; index <= this.PATHS_ROOT_PACKAGES.length; index += 1) {
const rootPackagesPath = this.PATHS_ROOT_PACKAGES[index];
// NOTICE: forest-express has not been sym linked.
if (this.dirname.includes(rootPackagesPath)) {
const indexRootPath = this.dirname.indexOf(rootPackagesPath);
const pathProjectRoot = this.dirname.substr(0, indexRootPath);
const subPathsToProject = pathProjectRoot.split(this.path.sep);
return this.ensureAbsolutePath(subPathsToProject);
}
}

// NOTICE: forest-express is sym linked, assuming the process is running on project directory.
return process.cwd();
}
}

module.exports = ProjectDirectoryFinder;
43 changes: 0 additions & 43 deletions src/utils/project-directory.js

This file was deleted.

28 changes: 26 additions & 2 deletions test/services/config-store.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,23 @@ describe('services > config-store', () => {
};

const fs = {
existsSync: jest.fn((dir) => dir.includes('forest')),
existsSync: jest.fn((directory) => directory.includes('forest')),
};

const path = {
resolve: jest.fn((root, dir) => `${root}/${dir}`),
};

const configStore = new ConfigStore({ logger, fs, path });
const projectDirectoryFinder = {
getAbsolutePath: jest.fn(() => './forest'),
};

const configStore = new ConfigStore({
logger,
fs,
path,
projectDirectoryFinder,
});

describe('when the given configuration is invalid', () => {
it('should log an error when no options are provided', () => {
Expand Down Expand Up @@ -120,6 +129,21 @@ describe('services > config-store', () => {

expect(() => configStore.validateOptions()).toThrow('The excludedModels option seems incorrectly set. Please check it is an array of model names.');
});

it('should log an error when schemaDir does not exist', () => {
expect.assertions(1);
jest.clearAllMocks();

const schemaDir = new Date();
configStore.lianaOptions = {
authSecret,
envSecret,
connections: {},
schemaDir,
};

expect(() => configStore.validateOptions()).toThrow(`Your schemaDir ("./${schemaDir}") does not exist. Please make sure it is set correctly.`);
});
});

describe('when the given configuration is valid', () => {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,19 +1,22 @@
const sinon = require('sinon');
const ProjectDirectoryUtils = require('../../src/utils/project-directory');
const context = require('../../src/context/');
const initContext = require('../../src/context/init');

describe('utils > project-directory', () => {
context.init(initContext);
const { projectDirectoryFinder } = context.inject();

describe('services > project-directory-finder', () => {
describe('using POSIX based OS', () => {
describe('running the app outside of its directory', () => {
describe('using a NPM install', () => {
it('should still return the absolute path of the project directory', () => {
expect.assertions(1);

const projectDirectoryUtils = new ProjectDirectoryUtils();
const cwdStub = sinon.stub(process, 'cwd');
cwdStub.returns('/Users/forestUser/projects');
sinon.replace(projectDirectoryUtils, 'dirname', '/Users/forestUser/projects/myLumberProject/node_modules/forest-express/dist/utils');
sinon.replace(projectDirectoryFinder, 'dirname', '/Users/forestUser/projects/myLumberProject/node_modules/forest-express/dist/utils');

const absoluteProjectPath = projectDirectoryUtils.getAbsolutePath();
const absoluteProjectPath = projectDirectoryFinder.getAbsolutePath();

expect(absoluteProjectPath).toStrictEqual('/Users/forestUser/projects/myLumberProject');

Expand All @@ -27,12 +30,11 @@ describe('utils > project-directory', () => {
it('should return the absolute path of the project directory', () => {
expect.assertions(1);

const projectDirectoryUtils = new ProjectDirectoryUtils();
const cwdStub = sinon.stub(process, 'cwd');
cwdStub.returns('/Users/forest/User/projects');
sinon.replace(projectDirectoryUtils, 'dirname', '/Users/forestUser/projects/myLumberProject/.yarn/cache/forest-express-npm-8.3.1-a520e9a060-7158678646.zip/node_modules/forest-express/dist/utils');
sinon.replace(projectDirectoryFinder, 'dirname', '/Users/forestUser/projects/myLumberProject/.yarn/cache/forest-express-npm-8.3.1-a520e9a060-7158678646.zip/node_modules/forest-express/dist/utils');

const absoluteProjectPath = projectDirectoryUtils.getAbsolutePath();
const absoluteProjectPath = projectDirectoryFinder.getAbsolutePath();

expect(absoluteProjectPath).toStrictEqual('/Users/forestUser/projects/myLumberProject');

Expand All @@ -45,12 +47,11 @@ describe('utils > project-directory', () => {
it('should return the absolute path of the project directory', () => {
expect.assertions(1);

const projectDirectoryUtils = new ProjectDirectoryUtils();
const cwdStub = sinon.stub(process, 'cwd');
cwdStub.returns('/Users/forestUser/projects');
sinon.replace(projectDirectoryUtils, 'dirname', '/Users/forestUser/projects/myLumberProject/.yarn/unplugged/forest-express-npm-8.3.1-a520e9a060-7158678646/dist/utils');
sinon.replace(projectDirectoryFinder, 'dirname', '/Users/forestUser/projects/myLumberProject/.yarn/unplugged/forest-express-npm-8.3.1-a520e9a060-7158678646/dist/utils');

const absoluteProjectPath = projectDirectoryUtils.getAbsolutePath();
const absoluteProjectPath = projectDirectoryFinder.getAbsolutePath();

expect(absoluteProjectPath).toStrictEqual('/Users/forestUser/projects/myLumberProject');

Expand All @@ -66,12 +67,11 @@ describe('utils > project-directory', () => {
it('should return the absolute path of the project directory', () => {
expect.assertions(1);

const projectDirectoryUtils = new ProjectDirectoryUtils();
const cwdStub = sinon.stub(process, 'cwd');
cwdStub.returns('/Users/forestUser/projects/myLumberProject');
sinon.replace(projectDirectoryUtils, 'dirname', '/Users/forestUser/projects/myLumberProject/node_modules/forest-express/dist/utils');
sinon.replace(projectDirectoryFinder, 'dirname', '/Users/forestUser/projects/myLumberProject/node_modules/forest-express/dist/utils');

const absoluteProjectPath = projectDirectoryUtils.getAbsolutePath();
const absoluteProjectPath = projectDirectoryFinder.getAbsolutePath();

expect(absoluteProjectPath).toStrictEqual('/Users/forestUser/projects/myLumberProject');

Expand All @@ -85,12 +85,11 @@ describe('utils > project-directory', () => {
it('should return the absolute path of the project directory', () => {
expect.assertions(1);

const projectDirectoryUtils = new ProjectDirectoryUtils();
const cwdStub = sinon.stub(process, 'cwd');
cwdStub.returns('/Users/forestUser/projects/myLumberProject');
sinon.replace(projectDirectoryUtils, 'dirname', '/Users/forestUser/projects/myLumberProject/.yarn/cache/forest-express-npm-8.3.1-a520e9a060-7158678646.zip/node_modules/forest-express/dist/utils');
sinon.replace(projectDirectoryFinder, 'dirname', '/Users/forestUser/projects/myLumberProject/.yarn/cache/forest-express-npm-8.3.1-a520e9a060-7158678646.zip/node_modules/forest-express/dist/utils');

const absoluteProjectPath = projectDirectoryUtils.getAbsolutePath();
const absoluteProjectPath = projectDirectoryFinder.getAbsolutePath();

expect(absoluteProjectPath).toStrictEqual('/Users/forestUser/projects/myLumberProject');

Expand All @@ -103,12 +102,11 @@ describe('utils > project-directory', () => {
it('should return the absolute path of the project directory', () => {
expect.assertions(1);

const projectDirectoryUtils = new ProjectDirectoryUtils();
const cwdStub = sinon.stub(process, 'cwd');
cwdStub.returns('/Users/forestUser/projects/myLumberProject');
sinon.replace(projectDirectoryUtils, 'dirname', '/Users/forestUser/projects/myLumberProject/.yarn/unplugged/forest-express-npm-8.3.1-a520e9a060-7158678646/dist/utils');
sinon.replace(projectDirectoryFinder, 'dirname', '/Users/forestUser/projects/myLumberProject/.yarn/unplugged/forest-express-npm-8.3.1-a520e9a060-7158678646/dist/utils');

const absoluteProjectPath = projectDirectoryUtils.getAbsolutePath();
const absoluteProjectPath = projectDirectoryFinder.getAbsolutePath();

expect(absoluteProjectPath).toStrictEqual('/Users/forestUser/projects/myLumberProject');

Expand All @@ -123,10 +121,9 @@ describe('utils > project-directory', () => {
it('should return the current working directory', () => {
expect.assertions(1);

const projectDirectoryUtils = new ProjectDirectoryUtils();
sinon.replace(projectDirectoryUtils, 'dirname', '/Users/forestUser/projects/forest-express/dist/utils/project-directory');
sinon.replace(projectDirectoryFinder, 'dirname', '/Users/forestUser/projects/forest-express/dist/utils/project-directory');

const absoluteProjectPath = projectDirectoryUtils.getAbsolutePath();
const absoluteProjectPath = projectDirectoryFinder.getAbsolutePath();

expect(absoluteProjectPath).toStrictEqual(process.cwd());

Expand Down

0 comments on commit c27bfb9

Please sign in to comment.