feat: Fetch, install and build @lowdefy/server from CLI.

This commit is contained in:
Sam Tolmay 2021-11-25 17:31:03 +02:00
parent c97a8fa6b5
commit 7966538468
No known key found for this signature in database
GPG Key ID: D004126FCD1A6DF0
13 changed files with 321 additions and 111 deletions

View File

@ -14,33 +14,17 @@
limitations under the License.
*/
import path from 'path';
import fse from 'fs-extra';
import getFederatedModule from '../../utils/getFederatedModule';
import getServer from './getServer.js';
import installServer from './installServer.js';
import runLowdefyBuild from './runLowdefyBuild.js';
async function build({ context }) {
const { default: buildScript } = await getFederatedModule({
module: 'build',
packageName: '@lowdefy/build',
version: context.lowdefyVersion,
context,
});
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.options.blocksServerUrl,
buildDirectory: context.buildDirectory,
cacheDirectory: context.cacheDirectory,
configDirectory: context.baseDirectory,
logger: context.print,
refResolver: context.options.refResolver,
});
await getServer({ context });
await installServer({ context });
await runLowdefyBuild({ context });
await context.sendTelemetry();
context.print.log(`Build artifacts saved at ${context.buildDirectory}.`);
context.print.succeed(`Build successful.`);
}

View File

@ -0,0 +1,51 @@
/*
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 fs from 'fs';
import path from 'path';
import { cleanDirectory, readFile } from '@lowdefy/node-utils';
import fetchNpmTarball from '../../utils/fetchNpmTarball.js';
async function getServer({ context }) {
let fetchServer = false;
const serverExists = fs.existsSync(path.join(context.directories.server, 'package.json'));
if (!serverExists) fetchServer = true;
if (serverExists) {
const serverPackageConfig = JSON.parse(
await readFile(path.join(context.directories.server, 'package.json'))
);
if (serverPackageConfig.version !== context.lowdefyVersion) {
fetchServer = true;
context.print.warn(`Removing @lowdefy/server with version ${serverPackageConfig.version}`);
await cleanDirectory(context.directories.server);
}
}
if (fetchServer) {
context.print.spin('Fetching @lowdefy/server from npm.');
await fetchNpmTarball({
packageName: '@lowdefy/server',
version: context.lowdefyVersion,
directory: context.directories.server,
});
context.print.log('Fetched @lowdefy/server from npm.');
return;
}
}
export default getServer;

View File

@ -0,0 +1,49 @@
/*
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 execProcess from '../../utils/execProcess.js';
import spawnProcess from '../../utils/spawnProcess.js';
// const commands = {
// npm: 'npm install --legacy-peer-deps',
// yarn: 'yarn install',
// };
const args = {
npm: ['install', '--legacy-peer-deps'],
yarn: ['install'],
};
async function installServer({ context }) {
context.print.spin(`Running ${context.packageManager} install.`);
try {
await spawnProcess({
context,
command: context.packageManager, // npm or yarn
args: args[context.packageManager],
processOptions: {
cwd: context.directories.server,
},
silent: false,
});
} catch (error) {
console.log(error);
throw new Error(`${context.packageManager} install failed.`);
}
context.print.log(`${context.packageManager} install successful.`);
}
export default installServer;

View File

@ -0,0 +1,41 @@
/*
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 execProcess from '../../utils/execProcess.js';
const commands = {
npm: 'npm run build:lowdefy',
yarn: 'yarn run build:lowdefy',
};
async function runLowdefyBuild({ context }) {
context.print.log('Running Lowdefy build.');
try {
await execProcess({
context,
command: commands[context.packageManager],
processOptions: {
cwd: context.directories.server,
},
silent: false,
});
} catch (error) {
throw new Error('Lowdefy build failed.');
}
context.print.log('Lowdefy build successful.');
}
export default runLowdefyBuild;

View File

@ -17,7 +17,7 @@
import { readFile } from '@lowdefy/node-utils';
import program from 'commander';
// import build from './commands/build/build.js';
import build from './commands/build/build.js';
// import dev from './commands/dev/dev.js';
import init from './commands/init/init.js';
import runCommand from './utils/runCommand.js';
@ -29,28 +29,32 @@ const { description, version } = packageJson;
program.name('lowdefy').description(description).version(version, '-v, --version');
// program
// .command('build')
// .description('Build a Lowdefy deployment.')
// .usage(`[options]`)
// .option(
// '--base-directory <base-directory>',
// 'Change base directory. Default is the current working directory.'
// )
// .option(
// '--blocks-server-url <blocks-server-url>',
// 'The URL from where Lowdefy blocks will be served.'
// )
// .option('--disable-telemetry', 'Disable telemetry.')
// .option(
// '--output-directory <output-directory>',
// 'Change the directory to which build artifacts are saved. Default is "<base-directory>/.lowdefy/build".'
// )
// .option(
// '--ref-resolver <ref-resolver-function-path>',
// 'Path to a JavaScript file containing a _ref resolver function to be used as the app default _ref resolver.'
// )
// .action(runCommand(build));
program
.command('build')
.description('Build a Lowdefy deployment.')
.usage(`[options]`)
.option(
'--base-directory <base-directory>',
'Change base directory. Default is the current working directory.'
)
.option(
'--blocks-server-url <blocks-server-url>',
'The URL from where Lowdefy blocks will be served.'
)
.option('--disable-telemetry', 'Disable telemetry.')
.option(
'--output-directory <output-directory>',
'Change the directory to which build artifacts are saved. Default is "<base-directory>/.lowdefy/build".'
)
.option(
'--package-manager <package-manager>',
'The package manager to user. Options are "npm" or "yarn".'
)
.option(
'--ref-resolver <ref-resolver-function-path>',
'Path to a JavaScript file containing a _ref resolver function to be used as the app default _ref resolver.'
)
.action(runCommand({ cliVersion: version })(build));
// program
// .command('dev')
@ -78,7 +82,7 @@ program.name('lowdefy').description(description).version(version, '-v, --version
// '--watch-ignore <paths...>',
// 'A list of paths to files or directories that should be ignored by the file watcher. Globs are supported.'
// )
// .action(runCommand(dev));
// .action(runCommand({ cliVersion: version })(dev));
program
.command('init')

View File

@ -1,50 +0,0 @@
/*
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 checkChildProcessError from './checkChildProcessError.js';
const mockError = jest.fn();
const context = {
print: {
error: mockError,
},
};
test('output status 0', () => {
checkChildProcessError({ context, processOutput: { status: 0 }, message: 'Test Error Message' });
// checkChildProcessError should not throw, expect is so that test passes
expect(true).toBe(true);
});
test('output status 1', () => {
const processOutput = {
status: 1,
stderr: Buffer.from('Process error message'),
};
expect(() =>
checkChildProcessError({ context, processOutput, message: 'Test Error Message' })
).toThrow('Test Error Message');
expect(mockError.mock.calls).toMatchInlineSnapshot(
[['Process error message']],
`
Array [
Array [
"Process error message",
],
]
`
);
});

View File

@ -14,11 +14,25 @@
limitations under the License.
*/
function checkChildProcessError({ context, processOutput, message }) {
if (processOutput.status === 1) {
context.print.error(processOutput.stderr.toString('utf8'));
throw new Error(message);
import util from 'util';
import { exec } from 'child_process';
const execPromise = util.promisify(exec);
async function execProcess({ context, command, processOptions, silent }) {
const { stdout, stderr } = await execPromise(command, processOptions);
if (!silent) {
stderr.split('\n').forEach((line) => {
if (line) {
context.print.warn(line);
}
});
stdout.split('\n').forEach((line) => {
if (line) {
context.print.log(line);
}
});
}
}
export default checkChildProcessError;
export default execProcess;

View File

@ -59,6 +59,7 @@ async function fetchNpmTarball({ packageName, version, directory }) {
}
await decompress(tarball.data, directory, {
plugins: [decompressTargz()],
strip: 1, // Removes leading /package dir from the file path
});
}

View File

@ -17,13 +17,13 @@
import path from 'path';
function getDirectories({ baseDirectory, options }) {
let buildDirectory;
let dotLowdefy;
if (options.outputDirectory) {
buildDirectory = path.resolve(options.outputDirectory);
dotLowdefy = path.resolve(options.outputDirectory);
} else {
buildDirectory = path.resolve(baseDirectory, './.lowdefy/build');
dotLowdefy = path.resolve(baseDirectory, '.lowdefy');
}
return { buildDirectory };
return { dotLowdefy, server: path.join(dotLowdefy, 'server') };
}
export default getDirectories;

View File

@ -0,0 +1,59 @@
/*
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.
*/
// Source https://github.com/vercel/next.js/blob/canary/packages/create-next-app/helpers/should-use-yarn.ts
/*
The MIT License (MIT)
Copyright (c) 2021 Vercel, Inc.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
import { execSync } from 'child_process';
function getPackageManager({ options }) {
if (options.packageManager) return options.packageManager;
try {
const userAgent = process.env.npm_config_user_agent;
if (userAgent && userAgent.startsWith('yarn')) {
return 'yarn';
}
execSync('yarnpkg --version', { stdio: 'ignore' });
return 'yarn';
} catch (e) {
return 'npm';
}
}
export default getPackageManager;

View File

@ -0,0 +1,58 @@
/*
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 { spawn } from 'child_process';
async function spawnProcess({ context, command, args, processOptions, silent }) {
return new Promise((resolve, reject) => {
const process = spawn(command, args, processOptions);
process.stdout.on('data', (data) => {
if (!silent) {
data
.toString('utf8')
.split('\n')
.forEach((line) => {
if (line) {
context.print.log(line);
}
});
}
});
process.stderr.on('data', (data) => {
if (!silent) {
data
.toString('utf8')
.split('\n')
.forEach((line) => {
if (line) {
context.print.warn(line);
}
});
}
});
process.on('exit', (code) => {
if (code !== 0) {
reject(new Error(`${command} exited with code ${code}`));
}
resolve();
});
});
}
export default spawnProcess;

View File

@ -22,6 +22,7 @@ import getCliJson from './getCliJson.js';
import getDirectories from './getDirectories.js';
import getLowdefyYaml from './getLowdefyYaml.js';
import getOptions from './getOptions.js';
import getPackageManager from './getPackageManager.js';
import getSendTelemetry from './getSendTelemetry.js';
import createPrint from './print.js';
@ -39,10 +40,8 @@ async function startUp({ context, options = {}, command }) {
context.appId = appId;
context.options = getOptions(context);
const { buildDirectory } = getDirectories(context);
context.buildDirectory = buildDirectory;
context.directories = getDirectories(context);
context.packageManager = getPackageManager(context);
await checkForUpdatedVersions(context);
context.sendTelemetry = getSendTelemetry(context);

View File

@ -29,7 +29,7 @@
"src/*"
],
"scripts": {
"build": "yarn build:lowdefy && yarn build:next",
"build": "lowdefy-build && next build",
"build:lowdefy": "lowdefy-build",
"build:next": "next build",
"dev": "next dev",