diff --git a/packages/cli/src/commands/build/build.js b/packages/cli/src/commands/build/build.js index b205b8517..b7db94350 100644 --- a/packages/cli/src/commands/build/build.js +++ b/packages/cli/src/commands/build/build.js @@ -16,11 +16,9 @@ import path from 'path'; import fse from 'fs-extra'; -import startUp from '../../utils/startUp'; import getFederatedModule from '../../utils/getFederatedModule'; -async function build({ context, options }) { - await startUp({ context, options, command: 'build' }); +async function build({ context }) { const { default: buildScript } = await getFederatedModule({ module: 'build', packageName: '@lowdefy/build', @@ -30,10 +28,11 @@ async function build({ context, options }) { context.print.log( `Cleaning block meta cache at "${path.resolve(context.cacheDirectory, './meta')}".` ); + await fse.emptyDir(path.resolve(context.cacheDirectory, './meta')); context.print.info('Starting build.'); await buildScript({ - blocksServerUrl: context.blocksServerUrl, + blocksServerUrl: context.options.blocksServerUrl, cacheDirectory: context.cacheDirectory, configDirectory: context.baseDirectory, logger: context.print, diff --git a/packages/cli/src/commands/build/build.test.js b/packages/cli/src/commands/build/build.test.js index 0af4ab426..574fd91a1 100644 --- a/packages/cli/src/commands/build/build.test.js +++ b/packages/cli/src/commands/build/build.test.js @@ -14,11 +14,9 @@ limitations under the License. */ -import path from 'path'; import build from './build'; // eslint-disable-next-line no-unused-vars import getFederatedModule from '../../utils/getFederatedModule'; -// eslint-disable-next-line no-unused-vars import startUp from '../../utils/startUp'; jest.mock('../../utils/getFederatedModule', () => { @@ -31,11 +29,12 @@ jest.mock('../../utils/getFederatedModule', () => { jest.mock('../../utils/startUp'); test('build', async () => { - const cacheDirectory = path.resolve(process.cwd(), '.lowdefy/.cache'); - const outputDirectory = path.resolve(process.cwd(), '.lowdefy/build'); - await build({ context: {} }); + const context = {}; + await startUp({ context, options: {}, command: {} }); + await build({ context }); + const { default: buildScript } = getFederatedModule(); expect(buildScript).toHaveBeenCalledTimes(1); - expect(buildScript.mock.calls[0][0].outputDirectory).toEqual(outputDirectory); - expect(buildScript.mock.calls[0][0].cacheDirectory).toEqual(cacheDirectory); + expect(buildScript.mock.calls[0][0].outputDirectory).toEqual('baseDirectory/outputDirectory'); + expect(buildScript.mock.calls[0][0].cacheDirectory).toEqual('baseDirectory/cacheDirectory'); }); diff --git a/packages/cli/src/commands/buildNetlify/buildNetlify.js b/packages/cli/src/commands/buildNetlify/buildNetlify.js index e7d0fa851..be3cc2dd1 100644 --- a/packages/cli/src/commands/buildNetlify/buildNetlify.js +++ b/packages/cli/src/commands/buildNetlify/buildNetlify.js @@ -20,22 +20,24 @@ import fse from 'fs-extra'; import { readFile, writeFile } from '@lowdefy/node-utils'; import checkChildProcessError from '../../utils/checkChildProcessError'; -import startUp from '../../utils/startUp'; import getFederatedModule from '../../utils/getFederatedModule'; import fetchNpmTarball from '../../utils/fetchNpmTarball'; -async function fetchNetlifyServer({ context, netlifyDir }) { +async function fetchNetlifyServer({ context }) { context.print.log('Fetching Lowdefy Netlify server.'); await fetchNpmTarball({ packageName: '@lowdefy/server-netlify', version: context.lowdefyVersion, - directory: netlifyDir, + directory: context.netlifyDir, }); context.print.log('Fetched Lowdefy Netlify server.'); } -async function npmInstall({ context, netlifyDir }) { - await fse.copy(path.resolve(netlifyDir, 'package/package.json'), path.resolve('./package.json')); +async function npmInstall({ context }) { + await fse.copy( + path.resolve(context.netlifyDir, 'package/package.json'), + path.resolve('./package.json') + ); await fse.remove(path.resolve('./package-lock.json')); await fse.remove(path.resolve('./package-lock.json')); await fse.emptyDir(path.resolve('./node_modules')); @@ -63,9 +65,12 @@ async function fetchBuildScript({ context }) { return buildScript; } -async function build({ context, buildScript, netlifyDir }) { +async function build({ context, buildScript }) { context.print.log('Starting Lowdefy build.'); - const outputDirectory = path.resolve(netlifyDir, './package/dist/functions/graphql/build'); + const outputDirectory = path.resolve( + context.netlifyDir, + './package/dist/functions/graphql/build' + ); await buildScript({ blocksServerUrl: context.blocksServerUrl, cacheDirectory: context.cacheDirectory, @@ -91,17 +96,17 @@ async function buildIndexHtml({ context }) { context.print.log('Lowdefy index.html build complete.'); } -async function moveBuildArtifacts({ context, netlifyDir }) { +async function moveBuildArtifacts({ context }) { await fse.copy( - path.resolve(netlifyDir, 'package/dist/shell'), + path.resolve(context.netlifyDir, 'package/dist/shell'), path.resolve('./.lowdefy/publish') ); context.print.log(`Netlify publish artifacts moved to "./lowdefy/publish".`); } -async function moveFunctions({ context, netlifyDir }) { +async function moveFunctions({ context }) { await fse.copy( - path.resolve(netlifyDir, 'package/dist/functions'), + path.resolve(context.netlifyDir, 'package/dist/functions'), path.resolve('./.lowdefy/functions') ); context.print.log(`Netlify functions artifacts moved to "./lowdefy/functions".`); @@ -114,24 +119,20 @@ async function movePublicAssets({ context }) { context.print.log(`Public assets moved to "./lowdefy/publish/public".`); } -async function buildNetlify({ context, options }) { - if (process.env.NETLIFY === 'true') { - options.basicPrint = true; - } - await startUp({ context, options, command: 'build-netlify' }); - const netlifyDir = path.resolve(context.baseDirectory, './.lowdefy/netlify'); +async function buildNetlify({ context }) { + context.netlifyDir = path.resolve(context.baseDirectory, './.lowdefy/netlify'); context.print.info('Starting build.'); const buildScript = await fetchBuildScript({ context }); - await build({ context, buildScript, netlifyDir }); + await build({ context, buildScript }); context.print.info('Installing Lowdefy server.'); - await fetchNetlifyServer({ context, netlifyDir }); - await npmInstall({ context, netlifyDir }); + await fetchNetlifyServer({ context }); + await npmInstall({ context }); context.print.log(`Moving artifacts.`); - await moveBuildArtifacts({ context, netlifyDir }); - await moveFunctions({ context, netlifyDir }); + await moveBuildArtifacts({ context }); + await moveFunctions({ context }); await movePublicAssets({ context }); context.print.log(`Build artifacts.`); diff --git a/packages/cli/src/commands/cleanCache/cleanCache.js b/packages/cli/src/commands/cleanCache/cleanCache.js index e20d811ce..746d117b1 100644 --- a/packages/cli/src/commands/cleanCache/cleanCache.js +++ b/packages/cli/src/commands/cleanCache/cleanCache.js @@ -15,10 +15,8 @@ */ import fse from 'fs-extra'; -import startUp from '../../utils/startUp'; -async function cleanCache({ context, options }) { - await startUp({ context, options, command: 'clean-cache' }); +async function cleanCache({ context }) { context.print.log(`Cleaning cache at "${context.cacheDirectory}".`); await fse.emptyDir(context.cacheDirectory); await context.sendTelemetry(); diff --git a/packages/cli/src/commands/cleanCache/cleanCache.test.js b/packages/cli/src/commands/cleanCache/cleanCache.test.js index 19be112ae..4eb35ee75 100644 --- a/packages/cli/src/commands/cleanCache/cleanCache.test.js +++ b/packages/cli/src/commands/cleanCache/cleanCache.test.js @@ -17,7 +17,6 @@ import path from 'path'; import fse from 'fs-extra'; import cleanCache from './cleanCache'; -// eslint-disable-next-line no-unused-vars import startUp from '../../utils/startUp'; jest.mock('fs-extra', () => { @@ -32,13 +31,15 @@ beforeEach(() => { }); test('cleanCache', async () => { - await cleanCache({ context: {} }); - const cachePath = path.resolve(process.cwd(), './.lowdefy/.cache'); - expect(fse.emptyDir.mock.calls).toEqual([[cachePath]]); + const context = {}; + await startUp({ context, options: {}, command: {} }); + await cleanCache({ context }); + expect(fse.emptyDir.mock.calls).toEqual([['baseDirectory/cacheDirectory']]); }); test('cleanCache baseDir', async () => { - await cleanCache({ context: {}, options: { baseDirectory: 'baseDir' } }); - const cachePath = path.resolve(process.cwd(), 'baseDir/.lowdefy/.cache'); - expect(fse.emptyDir.mock.calls).toEqual([[cachePath]]); + const context = {}; + await startUp({ context, options: { baseDirectory: 'baseDir' }, command: {} }); + await cleanCache({ context }); + expect(fse.emptyDir.mock.calls).toEqual([['baseDir/cacheDirectory']]); }); diff --git a/packages/cli/src/commands/dev/buildWatcher.js b/packages/cli/src/commands/dev/buildWatcher.js index 97c5e232c..611ddfc2b 100644 --- a/packages/cli/src/commands/dev/buildWatcher.js +++ b/packages/cli/src/commands/dev/buildWatcher.js @@ -17,8 +17,8 @@ import path from 'path'; import chokidar from 'chokidar'; import BatchChanges from '../../utils/BatchChanges'; -function buildWatcher({ build, context, options, reloadFn }) { - const { watch = [], watchIgnore = [] } = options; +function buildWatcher({ build, context, reloadFn }) { + const { watch = [], watchIgnore = [] } = context.options; const resolvedWatchPaths = watch.map((pathName) => path.resolve(pathName)); const buildCallback = async () => { diff --git a/packages/cli/src/commands/dev/dev.js b/packages/cli/src/commands/dev/dev.js index 0fffa93df..518301866 100644 --- a/packages/cli/src/commands/dev/dev.js +++ b/packages/cli/src/commands/dev/dev.js @@ -30,30 +30,32 @@ async function initialBuild({ context }) { return build; } -async function serverSetup({ context, options }) { +async function serverSetup({ context }) { const gqlServer = await getGraphQL({ context }); - return getExpress({ context, gqlServer, options }); + return getExpress({ context, gqlServer }); } -async function dev({ context, options }) { - await prepare({ context, options }); +async function dev({ context }) { + await prepare({ context }); const initialBuildPromise = initialBuild({ context }); - const serverSetupPromise = serverSetup({ context, options }); + const serverSetupPromise = serverSetup({ context }); const [build, { expressApp, reloadFn }] = await Promise.all([ initialBuildPromise, serverSetupPromise, ]); - buildWatcher({ build, context, options, reloadFn }); + buildWatcher({ build, context, reloadFn }); envWatcher({ context }); versionWatcher({ context }); context.print.log('Starting Lowdefy development server.'); - expressApp.listen(expressApp.get('port'), function () { - context.print.info(`Development server listening on port ${options.port}`); + + const port = expressApp.get('port'); + expressApp.listen(port, function () { + context.print.info(`Development server listening on port ${port}`); }); - opener(`http://localhost:${options.port}`); + opener(`http://localhost:${port}`); await context.sendTelemetry({ data: { diff --git a/packages/cli/src/commands/dev/getExpress.js b/packages/cli/src/commands/dev/getExpress.js index 8c91523fd..e8b04a75a 100644 --- a/packages/cli/src/commands/dev/getExpress.js +++ b/packages/cli/src/commands/dev/getExpress.js @@ -21,7 +21,7 @@ import { get } from '@lowdefy/helpers'; import { readFile } from '@lowdefy/node-utils'; import findOpenPort from '../../utils/findOpenPort'; -async function getExpress({ context, gqlServer, options }) { +async function getExpress({ context, gqlServer }) { const serveIndex = async (req, res) => { let indexHtml = await readFile(path.resolve(__dirname, 'shell/index.html')); let appConfig = await readFile(path.resolve(context.outputDirectory, 'app.json')); @@ -39,7 +39,8 @@ async function getExpress({ context, gqlServer, options }) { const app = express(); - app.set('port', parseInt(options.port)); + // port is initialized to 3000 in prepare function + app.set('port', parseInt(context.options.port)); gqlServer.applyMiddleware({ app, path: '/api/graphql' }); diff --git a/packages/cli/src/commands/dev/prepare.js b/packages/cli/src/commands/dev/prepare.js index 203fc7033..4be025bc3 100644 --- a/packages/cli/src/commands/dev/prepare.js +++ b/packages/cli/src/commands/dev/prepare.js @@ -17,13 +17,10 @@ import path from 'path'; import dotenv from 'dotenv'; import fse from 'fs-extra'; -import startUp from '../../utils/startUp'; - -async function prepare({ context, options }) { +async function prepare({ context }) { dotenv.config({ silent: true }); // Setup - if (!options.port) options.port = 3000; - await startUp({ context, options, command: 'dev' }); + if (!context.options.port) context.options.port = 3000; context.print.log( `Cleaning block meta cache at "${path.resolve(context.cacheDirectory, './meta')}".` ); diff --git a/packages/cli/src/commands/dev/versionWatcher.js b/packages/cli/src/commands/dev/versionWatcher.js index 7bebf775d..c5d097960 100644 --- a/packages/cli/src/commands/dev/versionWatcher.js +++ b/packages/cli/src/commands/dev/versionWatcher.js @@ -15,11 +15,11 @@ */ import chokidar from 'chokidar'; import BatchChanges from '../../utils/BatchChanges'; -import getConfig from '../../utils/getConfig'; +import getLowdefyYaml from '../../utils/getLowdefyYaml'; function versionWatcher({ context }) { const changeLowdefyFileCallback = async () => { - const { lowdefyVersion } = await getConfig(context); + const { lowdefyVersion } = await getLowdefyYaml(context); if (lowdefyVersion !== context.lowdefyVersion) { context.print.warn('Lowdefy version changed. You should restart your development server.'); process.exit(); diff --git a/packages/cli/src/commands/init/init.js b/packages/cli/src/commands/init/init.js index 096a5f687..c1ae17510 100644 --- a/packages/cli/src/commands/init/init.js +++ b/packages/cli/src/commands/init/init.js @@ -18,11 +18,9 @@ import path from 'path'; import fse from 'fs-extra'; import { writeFile } from '@lowdefy/node-utils'; -import startUp from '../../utils/startUp'; import lowdefyFile from './lowdefyFile'; -async function init({ context, options }) { - await startUp({ context, options, command: 'init', lowdefyFileNotRequired: true }); +async function init({ context }) { const lowdefyFilePath = path.resolve('./lowdefy.yaml'); const fileExists = fse.existsSync(lowdefyFilePath); if (fileExists) { diff --git a/packages/cli/src/index.js b/packages/cli/src/index.js index ad8da21f8..cbd896f29 100755 --- a/packages/cli/src/index.js +++ b/packages/cli/src/index.js @@ -39,6 +39,7 @@ program '--blocks-server-url ', 'The URL from where Lowdefy blocks will be served.' ) + .option('--disable-telemetry', 'Disable telemetry.') .option( '--output-directory ', 'Change the directory to which build artifacts are saved. Default is "/.lowdefy/build".' @@ -57,6 +58,7 @@ program '--blocks-server-url ', 'The URL from where Lowdefy blocks will be served.' ) + .option('--disable-telemetry', 'Disable telemetry.') .action(runCommand(buildNetlify)); program @@ -67,6 +69,7 @@ program '--base-directory ', 'Change base directory. Default is the current working directory.' ) + .option('--disable-telemetry', 'Disable telemetry.') .action(runCommand(cleanCache)); program @@ -81,6 +84,7 @@ program '--blocks-server-url ', 'The URL from where Lowdefy blocks will be served.' ) + .option('--disable-telemetry', 'Disable telemetry.') .option('--port ', 'Change the port the server is hosted at. Default is 3000.') .option( '--watch ', diff --git a/packages/cli/src/utils/__mocks__/startUp.js b/packages/cli/src/utils/__mocks__/startUp.js index 0737e6ea7..1821b10a6 100644 --- a/packages/cli/src/utils/__mocks__/startUp.js +++ b/packages/cli/src/utils/__mocks__/startUp.js @@ -14,28 +14,32 @@ limitations under the License. */ -import path from 'path'; -import { cacheDirectoryPath, outputDirectoryPath } from '../directories'; +const mockStartUp = jest.fn().mockImplementation(mockStartUpImp); -async function mockStartUp({ context, options = {} }) { +async function mockStartUpImp({ context, options = {} }) { + context.command = 'test'; context.cliVersion = 'cliVersion'; - context.appId = 'appId'; - context.disableTelemetry = false; - context.lowdefyVersion = 'lowdefyVersion'; - context.sendTelemetry = jest.fn(); + context.commandLineOptions = options; + context.print = { info: jest.fn(), succeed: jest.fn(), log: jest.fn(), }; - context.baseDirectory = path.resolve(options.baseDirectory || process.cwd()); - context.cacheDirectory = path.resolve(context.baseDirectory, cacheDirectoryPath); - if (options.outputDirectory) { - context.outputDirectory = path.resolve(options.outputDirectory); - } else { - context.outputDirectory = path.resolve(context.baseDirectory, outputDirectoryPath); - } + context.baseDirectory = options.baseDirectory || 'baseDirectory'; + + context.cliConfig = {}; + context.lowdefyVersion = 'lowdefyVersion'; + + context.appId = 'appId'; + context.options = options; + + context.cacheDirectory = `${context.baseDirectory}/cacheDirectory`; + context.outputDirectory = `${context.baseDirectory}/outputDirectory`; + + context.sendTelemetry = jest.fn(); + return context; } diff --git a/packages/cli/src/utils/directories.test.js b/packages/cli/src/utils/getDirectories.js similarity index 57% rename from packages/cli/src/utils/directories.test.js rename to packages/cli/src/utils/getDirectories.js index 0d0e7f600..365205b2f 100644 --- a/packages/cli/src/utils/directories.test.js +++ b/packages/cli/src/utils/getDirectories.js @@ -14,11 +14,18 @@ limitations under the License. */ -import * as directories from './directories'; +import path from 'path'; -test('directories', () => { - expect(directories).toEqual({ - cacheDirectoryPath: './.lowdefy/.cache', - outputDirectoryPath: './.lowdefy/build', - }); -}); +function getDirectories({ baseDirectory, options }) { + const cacheDirectory = path.resolve(baseDirectory, './.lowdefy/.cache'); + + let outputDirectory; + if (options.outputDirectory) { + outputDirectory = path.resolve(options.outputDirectory); + } else { + outputDirectory = path.resolve(baseDirectory, './.lowdefy/build'); + } + return { cacheDirectory, outputDirectory }; +} + +export default getDirectories; diff --git a/packages/cli/src/utils/getDirectories.test.js b/packages/cli/src/utils/getDirectories.test.js new file mode 100644 index 000000000..91cf1c632 --- /dev/null +++ b/packages/cli/src/utils/getDirectories.test.js @@ -0,0 +1,39 @@ +/* + Copyright 2020-2021 Lowdefy, Inc + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +import getDirectories from './getDirectories'; + +test('default directories', () => { + const { cacheDirectory, outputDirectory } = getDirectories({ + baseDirectory: '/test/base', + options: {}, + }); + + expect(cacheDirectory).toEqual('/test/base/.lowdefy/.cache'); + expect(outputDirectory).toEqual('/test/base/.lowdefy/build'); +}); + +test('specify outputDirectory in options', () => { + const { cacheDirectory, outputDirectory } = getDirectories({ + baseDirectory: '/test/base', + options: { + outputDirectory: '/test/build', + }, + }); + + expect(cacheDirectory).toEqual('/test/base/.lowdefy/.cache'); + expect(outputDirectory).toEqual('/test/build'); +}); diff --git a/packages/cli/src/utils/getConfig.js b/packages/cli/src/utils/getLowdefyYaml.js similarity index 76% rename from packages/cli/src/utils/getConfig.js rename to packages/cli/src/utils/getLowdefyYaml.js index cf2d09733..03059fc34 100644 --- a/packages/cli/src/utils/getConfig.js +++ b/packages/cli/src/utils/getLowdefyYaml.js @@ -18,14 +18,16 @@ import path from 'path'; import { get, type } from '@lowdefy/helpers'; import { readFile } from '@lowdefy/node-utils'; import YAML from 'js-yaml'; -import getCliJson from './getCliJson'; -async function getConfig(context) { - const lowdefyYaml = await readFile(path.resolve(context.baseDirectory, 'lowdefy.yaml')); +async function getLowdefyYaml({ baseDirectory, command }) { + const lowdefyYaml = await readFile(path.resolve(baseDirectory, 'lowdefy.yaml')); if (!lowdefyYaml) { - throw new Error( - `Could not find "lowdefy.yaml" file in specified base directory ${context.baseDirectory}.` - ); + if (!['init', 'clean-cache'].includes(command)) { + throw new Error( + `Could not find "lowdefy.yaml" file in specified base directory ${baseDirectory}.` + ); + } + return { cliConfig: {} }; } let lowdefy; try { @@ -45,12 +47,10 @@ async function getConfig(context) { )}.` ); } - const { appId } = await getCliJson(context); return { - appId, lowdefyVersion: lowdefy.lowdefy, - disableTelemetry: get(lowdefy, 'cli.disableTelemetry'), + cliConfig: get(lowdefy, 'cli', { default: {} }), }; } -export default getConfig; +export default getLowdefyYaml; diff --git a/packages/cli/src/utils/getConfig.test.js b/packages/cli/src/utils/getLowdefyYaml.test.js similarity index 72% rename from packages/cli/src/utils/getConfig.test.js rename to packages/cli/src/utils/getLowdefyYaml.test.js index 34cf8242f..2ee849b6b 100644 --- a/packages/cli/src/utils/getConfig.test.js +++ b/packages/cli/src/utils/getLowdefyYaml.test.js @@ -16,11 +16,7 @@ import path from 'path'; import { readFile } from '@lowdefy/node-utils'; -import getConfig from './getConfig'; -// eslint-disable-next-line no-unused-vars -import getCliJson from './getCliJson'; - -jest.mock('./getCliJson', () => () => ({ appId: 'appId' })); +import getLowdefyYaml from './getLowdefyYaml'; jest.mock('@lowdefy/node-utils', () => { const readFile = jest.fn(); @@ -46,8 +42,8 @@ test('get version from yaml file', async () => { } return null; }); - const config = await getConfig({ baseDirectory }); - expect(config).toEqual({ lowdefyVersion: '1.0.0', appId: 'appId' }); + const config = await getLowdefyYaml({ baseDirectory }); + expect(config).toEqual({ lowdefyVersion: '1.0.0', cliConfig: {} }); }); test('get version from yaml file, base dir specified', async () => { @@ -59,8 +55,8 @@ test('get version from yaml file, base dir specified', async () => { } return null; }); - const config = await getConfig({ baseDirectory: path.resolve(process.cwd(), './baseDir') }); - expect(config).toEqual({ lowdefyVersion: '1.0.0', appId: 'appId' }); + const config = await getLowdefyYaml({ baseDirectory: path.resolve(process.cwd(), './baseDir') }); + expect(config).toEqual({ lowdefyVersion: '1.0.0', cliConfig: {} }); }); test('could not find lowdefy.yaml in cwd', async () => { @@ -72,7 +68,7 @@ test('could not find lowdefy.yaml in cwd', async () => { lowdefy: 1.0.0 `; }); - await expect(getConfig({ baseDirectory })).rejects.toThrow( + await expect(getLowdefyYaml({ baseDirectory })).rejects.toThrow( 'Could not find "lowdefy.yaml" file in specified base directory' ); }); @@ -87,7 +83,7 @@ test('could not find lowdefy.yaml in base dir', async () => { `; }); await expect( - getConfig({ baseDirectory: path.resolve(process.cwd(), './baseDir') }) + getLowdefyYaml({ baseDirectory: path.resolve(process.cwd(), './baseDir') }) ).rejects.toThrow('Could not find "lowdefy.yaml" file in specified base directory'); }); @@ -102,7 +98,7 @@ test('lowdefy.yaml is invalid yaml', async () => { } return null; }); - await expect(getConfig({ baseDirectory })).rejects.toThrow( + await expect(getLowdefyYaml({ baseDirectory })).rejects.toThrow( 'Could not parse "lowdefy.yaml" file. Received error ' ); }); @@ -118,7 +114,7 @@ test('No version specified', async () => { } return null; }); - await expect(getConfig({ baseDirectory })).rejects.toThrow( + await expect(getLowdefyYaml({ baseDirectory })).rejects.toThrow( 'No version specified in "lowdefy.yaml" file. Specify a version in the "lowdefy" field.' ); }); @@ -132,7 +128,7 @@ test('Version is not a string', async () => { } return null; }); - await expect(getConfig({ baseDirectory })).rejects.toThrow( + await expect(getLowdefyYaml({ baseDirectory })).rejects.toThrow( 'Version number specified in "lowdefy.yaml" file is not valid. Received 1.' ); }); @@ -146,22 +142,39 @@ test('Version is not a valid version number', async () => { } return null; }); - await expect(getConfig({ baseDirectory })).rejects.toThrow( + await expect(getLowdefyYaml({ baseDirectory })).rejects.toThrow( 'Version number specified in "lowdefy.yaml" file is not valid. Received "v1-0-3".' ); }); -test('get disabled telemetry', async () => { +test('get cliConfig', async () => { readFile.mockImplementation((filePath) => { if (filePath === path.resolve(process.cwd(), 'lowdefy.yaml')) { return ` lowdefy: 1.0.0 cli: disableTelemetry: true + watch: + - a `; } return null; }); - const config = await getConfig({ baseDirectory }); - expect(config).toEqual({ lowdefyVersion: '1.0.0', disableTelemetry: true, appId: 'appId' }); + const config = await getLowdefyYaml({ baseDirectory }); + expect(config).toEqual({ + lowdefyVersion: '1.0.0', + cliConfig: { disableTelemetry: true, watch: ['a'] }, + }); +}); + +test('could not find lowdefy.yaml in base dir, command is "init" or "clean-cache"', async () => { + readFile.mockImplementation(() => null); + let config = await getLowdefyYaml({ command: 'init', baseDirectory }); + expect(config).toEqual({ + cliConfig: {}, + }); + config = await getLowdefyYaml({ command: 'clean-cache', baseDirectory }); + expect(config).toEqual({ + cliConfig: {}, + }); }); diff --git a/packages/cli/src/utils/directories.js b/packages/cli/src/utils/getOptions.js similarity index 70% rename from packages/cli/src/utils/directories.js rename to packages/cli/src/utils/getOptions.js index 1af34b5bf..14455b1f3 100644 --- a/packages/cli/src/utils/directories.js +++ b/packages/cli/src/utils/getOptions.js @@ -14,7 +14,14 @@ limitations under the License. */ -const cacheDirectoryPath = './.lowdefy/.cache'; -const outputDirectoryPath = './.lowdefy/build'; +function getOptions({ commandLineOptions, cliConfig }) { + // commandLineOptions take precedence over config in lowdefy.yaml + const options = { + ...cliConfig, + ...commandLineOptions, + }; -export { cacheDirectoryPath, outputDirectoryPath }; + return options; +} + +export default getOptions; diff --git a/packages/cli/src/utils/getSendTelemetry.js b/packages/cli/src/utils/getSendTelemetry.js index a2e89f9af..f27a23840 100644 --- a/packages/cli/src/utils/getSendTelemetry.js +++ b/packages/cli/src/utils/getSendTelemetry.js @@ -15,8 +15,9 @@ */ import axios from 'axios'; -function getSendTelemetry({ appId, cliVersion, command, disableTelemetry, lowdefyVersion }) { - if (disableTelemetry) { + +function getSendTelemetry({ appId, cliVersion, command, lowdefyVersion, options }) { + if (options.disableTelemetry) { return () => {}; } async function sendTelemetry({ data = {} } = {}) { diff --git a/packages/cli/src/utils/getSendTelemetry.test.js b/packages/cli/src/utils/getSendTelemetry.test.js index bfa52e09c..977df7b65 100644 --- a/packages/cli/src/utils/getSendTelemetry.test.js +++ b/packages/cli/src/utils/getSendTelemetry.test.js @@ -29,7 +29,9 @@ test('disable telemetry', async () => { const sendTelemetry = getSendTelemetry({ appId, cliVersion, - disableTelemetry: true, + options: { + disableTelemetry: true, + }, lowdefyVersion, }); await sendTelemetry({ data: { x: 1 } }); @@ -41,6 +43,7 @@ test('send telemetry', async () => { appId, cliVersion, lowdefyVersion, + options: {}, }); await sendTelemetry({ data: { x: 1 } }); expect(axios.request.mock.calls).toEqual([ @@ -70,6 +73,7 @@ test('send telemetry should not throw', async () => { appId, cliVersion, lowdefyVersion, + options: {}, }); await sendTelemetry({ data: { x: 1 } }); expect(true).toBe(true); diff --git a/packages/cli/src/utils/print.js b/packages/cli/src/utils/print.js index 4ab70e16b..66f7ea6e9 100644 --- a/packages/cli/src/utils/print.js +++ b/packages/cli/src/utils/print.js @@ -58,9 +58,9 @@ function createBasicPrint() { // Memoise print so that error handler can get the same spinner object let print; -function createPrint({ basic } = {}) { +function createPrint() { if (print) return print; - if (basic) { + if (process.env.CI === 'true') { print = createBasicPrint(); return print; } diff --git a/packages/cli/src/utils/print.test.js b/packages/cli/src/utils/print.test.js index 704ef2daf..915114b29 100644 --- a/packages/cli/src/utils/print.test.js +++ b/packages/cli/src/utils/print.test.js @@ -101,8 +101,11 @@ describe('memoise', () => { jest.isolateModules(() => { createPrint = require('./print').default; }); - const print = createPrint({ basic: true }); + const realCI = process.env.CI; + process.env.CI = 'true'; + const print = createPrint(); expect(print.type).toEqual('basic'); + process.env.CI = realCI; }); }); describe('ora print', () => { diff --git a/packages/cli/src/utils/runCommand.js b/packages/cli/src/utils/runCommand.js index dc98bddb5..04daffec5 100644 --- a/packages/cli/src/utils/runCommand.js +++ b/packages/cli/src/utils/runCommand.js @@ -15,12 +15,14 @@ */ import errorHandler from './errorHandler'; +import startUp from './startUp'; function runCommand(fn) { - async function run(options) { + async function run(options, command) { const context = {}; try { - const res = await fn({ context, options }); + await startUp({ context, options, command }); + const res = await fn({ context }); return res; } catch (error) { await errorHandler({ context, error }); diff --git a/packages/cli/src/utils/runCommand.test.js b/packages/cli/src/utils/runCommand.test.js index c3463f06e..e86ff55dc 100644 --- a/packages/cli/src/utils/runCommand.test.js +++ b/packages/cli/src/utils/runCommand.test.js @@ -16,8 +16,10 @@ import errorHandler from './errorHandler'; import runCommand from './runCommand'; +import startUp from './startUp'; jest.mock('./errorHandler'); +jest.mock('./startUp'); async function wait(ms) { return new Promise((resolve) => { @@ -29,10 +31,15 @@ beforeEach(() => { errorHandler.mockReset(); }); +const options = { option: true }; +const command = { + command: true, +}; + test('runCommand with synchronous function', async () => { const fn = jest.fn(() => 1 + 1); const wrapped = runCommand(fn); - const res = await wrapped(); + const res = await wrapped(options, command); expect(res).toBe(2); expect(fn).toHaveBeenCalled(); }); @@ -43,16 +50,79 @@ test('runCommand with asynchronous function', async () => { return 4; }); const wrapped = runCommand(fn); - const res = await wrapped(); + const res = await wrapped(options, command); expect(res).toBe(4); expect(fn).toHaveBeenCalled(); }); -test('Pass options and context to function', async () => { +test('runCommand calls startUp', async () => { const fn = jest.fn((...args) => args); const wrapped = runCommand(fn); - const res = await wrapped({ options: true }); - expect(res).toEqual([{ options: { options: true }, context: {} }]); + const res = await wrapped(options, command); + expect(res).toMatchInlineSnapshot(` + Array [ + Object { + "context": Object { + "appId": "appId", + "baseDirectory": "baseDirectory", + "cacheDirectory": "baseDirectory/cacheDirectory", + "cliConfig": Object {}, + "cliVersion": "cliVersion", + "command": "test", + "commandLineOptions": Object { + "option": true, + }, + "lowdefyVersion": "lowdefyVersion", + "options": Object { + "option": true, + }, + "outputDirectory": "baseDirectory/outputDirectory", + "print": Object { + "info": [MockFunction], + "log": [MockFunction], + "succeed": [MockFunction], + }, + "sendTelemetry": [MockFunction], + }, + }, + ] + `); + expect(startUp.mock.calls).toMatchInlineSnapshot(` + Array [ + Array [ + Object { + "command": Object { + "command": true, + }, + "context": Object { + "appId": "appId", + "baseDirectory": "baseDirectory", + "cacheDirectory": "baseDirectory/cacheDirectory", + "cliConfig": Object {}, + "cliVersion": "cliVersion", + "command": "test", + "commandLineOptions": Object { + "option": true, + }, + "lowdefyVersion": "lowdefyVersion", + "options": Object { + "option": true, + }, + "outputDirectory": "baseDirectory/outputDirectory", + "print": Object { + "info": [MockFunction], + "log": [MockFunction], + "succeed": [MockFunction], + }, + "sendTelemetry": [MockFunction], + }, + "options": Object { + "option": true, + }, + }, + ], + ] + `); }); test('Catch error synchronous function', async () => { @@ -60,16 +130,39 @@ test('Catch error synchronous function', async () => { throw new Error('Error'); }); const wrapped = runCommand(fn); - await wrapped(); + await wrapped(options, command); expect(fn).toHaveBeenCalled(); - expect(errorHandler.mock.calls).toEqual([ - [ - { - context: {}, - error: new Error('Error'), - }, - ], - ]); + expect(errorHandler.mock.calls).toMatchInlineSnapshot(` + Array [ + Array [ + Object { + "context": Object { + "appId": "appId", + "baseDirectory": "baseDirectory", + "cacheDirectory": "baseDirectory/cacheDirectory", + "cliConfig": Object {}, + "cliVersion": "cliVersion", + "command": "test", + "commandLineOptions": Object { + "option": true, + }, + "lowdefyVersion": "lowdefyVersion", + "options": Object { + "option": true, + }, + "outputDirectory": "baseDirectory/outputDirectory", + "print": Object { + "info": [MockFunction], + "log": [MockFunction], + "succeed": [MockFunction], + }, + "sendTelemetry": [MockFunction], + }, + "error": [Error: Error], + }, + ], + ] + `); }); test('Catch error asynchronous function', async () => { @@ -78,14 +171,37 @@ test('Catch error asynchronous function', async () => { throw new Error('Async Error'); }); const wrapped = runCommand(fn); - await wrapped(); + await wrapped(options, command); expect(fn).toHaveBeenCalled(); - expect(errorHandler.mock.calls).toEqual([ - [ - { - context: {}, - error: new Error('Async Error'), - }, - ], - ]); + expect(errorHandler.mock.calls).toMatchInlineSnapshot(` + Array [ + Array [ + Object { + "context": Object { + "appId": "appId", + "baseDirectory": "baseDirectory", + "cacheDirectory": "baseDirectory/cacheDirectory", + "cliConfig": Object {}, + "cliVersion": "cliVersion", + "command": "test", + "commandLineOptions": Object { + "option": true, + }, + "lowdefyVersion": "lowdefyVersion", + "options": Object { + "option": true, + }, + "outputDirectory": "baseDirectory/outputDirectory", + "print": Object { + "info": [MockFunction], + "log": [MockFunction], + "succeed": [MockFunction], + }, + "sendTelemetry": [MockFunction], + }, + "error": [Error: Async Error], + }, + ], + ] + `); }); diff --git a/packages/cli/src/utils/startUp.js b/packages/cli/src/utils/startUp.js index 93041113b..acfad6991 100644 --- a/packages/cli/src/utils/startUp.js +++ b/packages/cli/src/utils/startUp.js @@ -15,43 +15,49 @@ */ import path from 'path'; +import { type } from '@lowdefy/helpers'; + import checkForUpdatedVersions from './checkForUpdatedVersions'; -import getConfig from './getConfig'; +import getCliJson from './getCliJson'; +import getDirectories from './getDirectories'; +import getLowdefyYaml from './getLowdefyYaml'; +import getOptions from './getOptions'; import getSendTelemetry from './getSendTelemetry'; import createPrint from './print'; -import { cacheDirectoryPath, outputDirectoryPath } from './directories'; import packageJson from '../../package.json'; const { version: cliVersion } = packageJson; -async function startUp({ context, options = {}, command, lowdefyFileNotRequired }) { - context.command = command; +async function startUp({ context, options = {}, command }) { + context.command = command.name(); context.cliVersion = cliVersion; - context.print = createPrint({ - basic: options.basicPrint, - }); - + context.commandLineOptions = options; + context.print = createPrint(); context.baseDirectory = path.resolve(options.baseDirectory || process.cwd()); - context.cacheDirectory = path.resolve(context.baseDirectory, cacheDirectoryPath); - if (options.outputDirectory) { - context.outputDirectory = path.resolve(options.outputDirectory); - } else { - context.outputDirectory = path.resolve(context.baseDirectory, outputDirectoryPath); - } + const { cliConfig, lowdefyVersion } = await getLowdefyYaml(context); + context.cliConfig = cliConfig; + context.lowdefyVersion = lowdefyVersion; - context.blocksServerUrl = options.blocksServerUrl; + const { appId } = await getCliJson(context); + context.appId = appId; + + context.options = getOptions(context); + + const { cacheDirectory, outputDirectory } = getDirectories(context); + context.cacheDirectory = cacheDirectory; + context.outputDirectory = outputDirectory; - if (!lowdefyFileNotRequired) { - const { appId, disableTelemetry, lowdefyVersion } = await getConfig(context); - context.appId = appId; - context.disableTelemetry = disableTelemetry; - context.lowdefyVersion = lowdefyVersion; - context.print.log(`Running 'lowdefy ${command}'. Lowdefy app version ${lowdefyVersion}.`); - } else { - context.print.log(`Running 'lowdefy ${command}'.`); - } await checkForUpdatedVersions(context); + context.sendTelemetry = getSendTelemetry(context); + + if (type.isNone(lowdefyVersion)) { + context.print.log(`Running 'lowdefy ${context.command}'.`); + } else { + context.print.log( + `Running 'lowdefy ${context.command}'. Lowdefy app version ${lowdefyVersion}.` + ); + } return context; } diff --git a/packages/cli/src/utils/startUp.test.js b/packages/cli/src/utils/startUp.test.js index 419aae94a..868c18452 100644 --- a/packages/cli/src/utils/startUp.test.js +++ b/packages/cli/src/utils/startUp.test.js @@ -16,22 +16,23 @@ import path from 'path'; import startUp from './startUp'; -// eslint-disable-next-line no-unused-vars import checkForUpdatedVersions from './checkForUpdatedVersions'; -// eslint-disable-next-line no-unused-vars import createPrint from './print'; // eslint-disable-next-line no-unused-vars -import getConfig from './getConfig'; +import getLowdefyYaml from './getLowdefyYaml'; +// eslint-disable-next-line no-unused-vars +import getCliJson from './getCliJson'; // eslint-disable-next-line no-unused-vars import getSendTelemetry from './getSendTelemetry'; // eslint-disable-next-line no-unused-vars import packageJson from '../../package.json'; -jest.mock( - './getConfig', - () => async () => - Promise.resolve({ appId: 'appId', disableTelemetry: true, lowdefyVersion: 'lowdefyVersion' }) +jest.mock('./getLowdefyYaml', () => + jest.fn(async () => + Promise.resolve({ cliConfig: { cliConfig: true }, lowdefyVersion: 'lowdefyVersion' }) + ) ); +jest.mock('./getCliJson', () => async () => Promise.resolve({ appId: 'appId' })); jest.mock('./print', () => { const error = jest.fn(); const log = jest.fn(); @@ -42,55 +43,72 @@ jest.mock('./print', () => { }); jest.mock('../../package.json', () => ({ version: 'cliVersion' })); jest.mock('./getSendTelemetry', () => () => 'sendTelemetry'); -jest.mock('./checkForUpdatedVersions', () => () => 'checkForUpdatedVersions'); +jest.mock('./checkForUpdatedVersions', () => jest.fn(() => 'checkForUpdatedVersions')); const print = createPrint(); -test('startUp, options undefined', async () => { - const context = {}; - await startUp({ context, command: 'command' }); - expect(context).toEqual({ - appId: 'appId', - baseDirectory: path.resolve(process.cwd()), - cacheDirectory: path.resolve(process.cwd(), './.lowdefy/.cache'), - cliVersion: 'cliVersion', - command: 'command', - disableTelemetry: true, - lowdefyVersion: 'lowdefyVersion', - outputDirectory: path.resolve(process.cwd(), './.lowdefy/build'), - sendTelemetry: 'sendTelemetry', - print, - }); -}); +const command = { + name: () => 'test', +}; test('startUp, options empty', async () => { const context = {}; - await startUp({ context, options: {}, command: 'command' }); + await startUp({ context, options: {}, command }); expect(context).toEqual({ appId: 'appId', baseDirectory: path.resolve(process.cwd()), cacheDirectory: path.resolve(process.cwd(), './.lowdefy/.cache'), + cliConfig: { cliConfig: true }, cliVersion: 'cliVersion', - command: 'command', - disableTelemetry: true, + command: 'test', + commandLineOptions: {}, lowdefyVersion: 'lowdefyVersion', + options: { cliConfig: true }, outputDirectory: path.resolve(process.cwd(), './.lowdefy/build'), - sendTelemetry: 'sendTelemetry', print, + sendTelemetry: 'sendTelemetry', + }); + expect(checkForUpdatedVersions).toHaveBeenCalledTimes(1); + expect(print.log.mock.calls).toEqual([ + ["Running 'lowdefy test'. Lowdefy app version lowdefyVersion."], + ]); +}); + +test('startUp, options undefined', async () => { + const context = {}; + await startUp({ context, command }); + expect(context).toEqual({ + appId: 'appId', + baseDirectory: path.resolve(process.cwd()), + cacheDirectory: path.resolve(process.cwd(), './.lowdefy/.cache'), + cliConfig: { cliConfig: true }, + cliVersion: 'cliVersion', + command: 'test', + commandLineOptions: {}, + lowdefyVersion: 'lowdefyVersion', + options: { cliConfig: true }, + outputDirectory: path.resolve(process.cwd(), './.lowdefy/build'), + print, + sendTelemetry: 'sendTelemetry', }); }); test('startUp, options baseDirectory', async () => { const context = {}; - await startUp({ context, options: { baseDirectory: './baseDirectory' }, command: 'command' }); + await startUp({ context, options: { baseDirectory: './baseDirectory' }, command }); expect(context).toEqual({ appId: 'appId', baseDirectory: path.resolve(process.cwd(), 'baseDirectory'), cacheDirectory: path.resolve(process.cwd(), 'baseDirectory/.lowdefy/.cache'), + cliConfig: { cliConfig: true }, cliVersion: 'cliVersion', - command: 'command', - disableTelemetry: true, + command: 'test', + commandLineOptions: { baseDirectory: './baseDirectory' }, lowdefyVersion: 'lowdefyVersion', + options: { + cliConfig: true, + baseDirectory: './baseDirectory', + }, outputDirectory: path.resolve(process.cwd(), 'baseDirectory/.lowdefy/build'), sendTelemetry: 'sendTelemetry', print, @@ -99,15 +117,20 @@ test('startUp, options baseDirectory', async () => { test('startUp, options outputDirectory', async () => { const context = {}; - await startUp({ context, options: { outputDirectory: './outputDirectory' }, command: 'command' }); + await startUp({ context, options: { outputDirectory: './outputDirectory' }, command }); expect(context).toEqual({ appId: 'appId', baseDirectory: path.resolve(process.cwd()), cacheDirectory: path.resolve(process.cwd(), './.lowdefy/.cache'), + cliConfig: { cliConfig: true }, cliVersion: 'cliVersion', - command: 'command', - disableTelemetry: true, + command: 'test', + commandLineOptions: { outputDirectory: './outputDirectory' }, lowdefyVersion: 'lowdefyVersion', + options: { + cliConfig: true, + outputDirectory: './outputDirectory', + }, outputDirectory: path.resolve(process.cwd(), 'outputDirectory'), sendTelemetry: 'sendTelemetry', print, @@ -122,33 +145,50 @@ test('startUp, options baseDirectory and outputDirectory', async () => { baseDirectory: './baseDirectory', outputDirectory: './outputDirectory', }, - command: 'command', + command, }); expect(context).toEqual({ appId: 'appId', baseDirectory: path.resolve(process.cwd(), 'baseDirectory'), cacheDirectory: path.resolve(process.cwd(), 'baseDirectory/.lowdefy/.cache'), + cliConfig: { cliConfig: true }, cliVersion: 'cliVersion', - command: 'command', - disableTelemetry: true, + command: 'test', + commandLineOptions: { + baseDirectory: './baseDirectory', + outputDirectory: './outputDirectory', + }, lowdefyVersion: 'lowdefyVersion', + options: { + baseDirectory: './baseDirectory', + cliConfig: true, + outputDirectory: './outputDirectory', + }, outputDirectory: path.resolve(process.cwd(), 'outputDirectory'), sendTelemetry: 'sendTelemetry', print, }); }); -test('startUp, lowdefyFileNotRequired true', async () => { +test('startUp, no lowdefyVersion returned', async () => { + getLowdefyYaml.mockImplementationOnce(() => ({ cliConfig: {} })); const context = {}; - await startUp({ context, options: {}, command: 'command', lowdefyFileNotRequired: true }); + await startUp({ context, options: {}, command }); expect(context).toEqual({ + appId: 'appId', baseDirectory: path.resolve(process.cwd()), cacheDirectory: path.resolve(process.cwd(), './.lowdefy/.cache'), + cliConfig: {}, cliVersion: 'cliVersion', - command: 'command', + command: 'test', + commandLineOptions: {}, + lowdefyVersion: undefined, + options: {}, outputDirectory: path.resolve(process.cwd(), './.lowdefy/build'), - sendTelemetry: 'sendTelemetry', print, + sendTelemetry: 'sendTelemetry', }); + expect(checkForUpdatedVersions).toHaveBeenCalledTimes(1); + expect(print.log.mock.calls).toEqual([["Running 'lowdefy test'."]]); }); diff --git a/packages/docs/concepts/cli.yaml b/packages/docs/concepts/cli.yaml index c3a5799f7..ec2ab0a7f 100644 --- a/packages/docs/concepts/cli.yaml +++ b/packages/docs/concepts/cli.yaml @@ -52,6 +52,7 @@ _ref: - `--base-directory `: Change base directory. The default is the current working directory. - `--blocks-server-url `: The URL from where Lowdefy blocks will be served. See below for more information. + - `--disable-telemetry`: Disable telemetry. - `--output-directory `: Change the directory to which build artifacts are saved. The default is `/.lowdefy/build`. ## build-netlify @@ -61,12 +62,14 @@ _ref: - `--base-directory `: Change base directory. The default is the current working directory (The base directory should rather be configured in the Netlify build settings). - `--blocks-server-url `: The URL from where Lowdefy blocks will be served. See below for more information. + - `--disable-telemetry`: Disable telemetry. ## clean-cache The Lowdefy CLI caches block metadata, and build and server scripts in the `.lowdefy/cache` directory. These cached files can be removed using the `clean-cache` command. - `--base-directory `: Change base directory. The default is the current working directory. + - `--disable-telemetry`: Disable telemetry. ## dev @@ -74,6 +77,7 @@ _ref: - `--base-directory `: Change base directory. The default is the current working directory. - `--blocks-server-url `: The URL from where Lowdefy blocks will be served. See below for more information. + - `--disable-telemetry`: Disable telemetry. - `--port `: Change the port the server is hosted at. The default is `3000`. - `--watch `: A list of paths to files or directories that should be watched for changes. - `--watch-ignore `: A list of paths to files or directories that should be ignored by the file watcher. Globs are supported. @@ -91,13 +95,26 @@ _ref: lowdefy dev --watch-ignore public/** ``` + # Configuration + + All the CLI options can either be set as command line options, or the `cli` config object in your `lowdefy.yaml` file. Options set as command line options take precedence over options set in the `lowdefy.yaml` file. The config in the `lowdefy.yaml` cannot be referenced using the `_ref` operator, but need to be set in the file itself. + + Options set in the `lowdefy.yaml` should be defined in camelCase. The options that can be set are: + - `blocksServerUrl: string`: The URL from where Lowdefy blocks will be served. See below for more information. + - `disableTelemetry: boolean`: Disable telemetry. + - `outputDirectory: string`: Change the directory to which build artifacts are saved. The default is `/.lowdefy/build`. + - `port: number`: Change the port the server is hosted at. The default is `3000`. + - `watch: string[]`: A list of paths to files or directories that should be watched for changes. + - `watchIgnore: string[]`: A list of paths to files or directories that should be ignored by the file watcher. Globs are supported. + + The `--base-directory` option cannot be set from the `lowdefy.yaml` file. # Telemetry The CLI collects usage and error information to help us fix bugs, prioritize features, and understand how Lowdefy is being used. - All telemetry can be disabled by setting the `disableTelemetry` flag in `cli` config object in your `lowdefy.yaml` file (this cannot be a reference to another file): + All telemetry can be disabled by setting the `disableTelemetry` flag in `cli` config object in your `lowdefy.yaml` file (this cannot be a reference to another file), or by using the `--disable-telemetry` command line flag.: ###### `lowdefy.yaml` ```yaml