Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WIP] system import #141

Open
wants to merge 4 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 27 additions & 4 deletions src/handlers/componentInstall.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import catchLater from '../util/catchLater';
*/
export default async function componentInstall(
name: string,
{ force, all }: InstallComponentHandlerOptions
{ force, all, subtheme }: InstallComponentHandlerOptions
): Promise<void> {
const emulsifyConfig = await getEmulsifyConfig();
if (!emulsifyConfig) {
Expand Down Expand Up @@ -89,10 +89,10 @@ export default async function componentInstall(
);
}

if (!name && !all) {
if (!name && !all && !subtheme) {
return log(
'error',
'Please specify a component to install, or pass --all to install all available components.'
'Please specify a component to install, or subtheme name through option --subtheme, or pass --all to install all available components.'
);
}

Expand All @@ -114,11 +114,34 @@ export default async function componentInstall(
])
);
}
// Pull subtheme marked modules.
else if (subtheme) {
const parentComponents = variantConf.components
.filter((component) => component.subtheme?.includes(subtheme))
.map((component) => component.name);
const componentsWithDependencies = buildComponentDependencyList(
variantConf.components,
parentComponents
);
componentsWithDependencies.forEach((componentName) => {
components.push([
componentName,
catchLater(
installComponentFromCache(
systemConf,
variantConf,
componentName,
force
)
),
]);
});
}
// If there is only one component to install, add one single promise for the single component.
else {
const componentsWithDependencies = buildComponentDependencyList(
variantConf.components,
name
[name]
);
componentsWithDependencies.forEach((componentName) => {
components.push([
Expand Down
185 changes: 185 additions & 0 deletions src/handlers/systemImport.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,185 @@
import type { InstallSystemHandlerOptions } from '@emulsify-cli/handlers';
import type { GitCloneOptions } from '@emulsify-cli/git';
import type { EmulsifySystem } from '@emulsify-cli/config';

import R from 'ramda';
import Ajv from 'ajv';
import addFormats from 'ajv-formats';
import {
EXIT_ERROR,
EXIT_SUCCESS,
EMULSIFY_SYSTEM_CONFIG_FILE,
} from '../lib/constants';
import log from '../lib/log';
import getAvailableSystems from '../util/system/getAvailableSystems';
import getGitRepoNameFromUrl from '../util/getGitRepoNameFromUrl';
import cloneIntoCache from '../util/cache/cloneIntoCache';
import installComponentFromCache from '../util/project/installComponentFromCache';
import getJsonFromCachedFile from '../util/cache/getJsonFromCachedFile';
import getEmulsifyConfig from '../util/project/getEmulsifyConfig';
import systemSchema from '../schemas/system.json';
import variantSchema from '../schemas/variant.json';
import buildComponentDependencyList from '../util/project/buildComponentDependencyList';

/**
* Helper function that uses InstallSystemHandlerOptions input to determine what
* system should be installed, if any.
*
* @param options InstallSystemHandlerOptions object.
*
* @returns GitCloneOptions or void, if no valid system could be found using the input.
*/
export async function getSystemRepoInfo(
name: string | void,
{ repository, checkout }: InstallSystemHandlerOptions
): Promise<(GitCloneOptions & { name: string }) | void> {
// If a repository and checkout were specified, use that to return system information.
if (repository && checkout) {
const repoName = getGitRepoNameFromUrl(repository);
if (repoName) {
return {
name: repoName,
repository,
checkout,
};
}
}

// If a name was provided, attempt to find an out-of-the-box system with
// the name, and use it to return system information.
if (name) {
const system = (await getAvailableSystems()).find(R.propEq('name', name));
if (system) {
return {
name,
repository: system.repository,
checkout: system.checkout,
};
}
}
}

/**
* Handler for the `system import` command.
*/
export default async function systemImport(): Promise<void> {
// @TODO: extract some of this into a common util.
// Attempt to load emulsify config. If none is found, this is not an Emulsify project.
const projectConfig = await getEmulsifyConfig();
if (!projectConfig) {
return log(
'error',
'No Emulsify project detected. You must run this command within an existing Emulsify project. For more information about creating Emulsify projects, run "emulsify init --help"',
EXIT_ERROR
);
}

// This is totaly wrong. You can't query for predefined and preset only system repo.
const repo = await getSystemRepoInfo(
'',
projectConfig.system as InstallSystemHandlerOptions
);
if (!repo) {
return log(
'error',
'Unable to download specified system. You must either specify a valid name of an out-of-the-box system using the --name flag, or specify a valid repository and branch/tag/commit using the --repository and --checkout flags.',
EXIT_ERROR
);
}

// Clone should work as middleware for get repo.
await cloneIntoCache('systems', [repo.name])({
repository: repo.repository,
checkout: repo.checkout,
});

// Load the system configuration file.
const systemConf: EmulsifySystem | void = await getJsonFromCachedFile(
'systems',
[repo.name],
EMULSIFY_SYSTEM_CONFIG_FILE
);

// If there is no configuration file within the system, error.
if (!systemConf) {
return log(
'error',
`The system you attempted to install (${repo.name}) is invalid, as it does not contain a valid configuration file.`,
EXIT_ERROR
);
}

// Validate the system configuration file.
try {
const ajv = new Ajv();
// This is unfortunate...
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore The ajv-formats typing is bad :(
// eslint-disable-next-line @typescript-eslint/no-unsafe-call
addFormats(ajv, ['uri']);
ajv.addSchema(variantSchema, 'variant.json');
const validate = ajv.compile(systemSchema);

if (!validate(systemConf)) {
throw validate.errors;
}
} catch (e) {
// We're logging to the console here instead of our normal logging mechanism
// in order to have more readable output from the AJV validation.
console.error('System configuration errors:', e);
return log(
'error',
`The system install failed due to the validation errors reported above. Please fix the the errors in the "${systemConf.name}" configuration and try again.`,
EXIT_ERROR
);
}

// Extract the variant name, and error if no variant is determinable.
const variantName: string | void = projectConfig.project.platform;
if (!variantName) {
return log(
'error',
'Unable to determine a variant for the specified system. Please either pass in a valid variant using the --variant flag.',
EXIT_ERROR
);
}

// @TODO: clone variants into their own cache bucket if a reference is provided.
const variantConf = systemConf.variants?.find(
({ platform }) => platform === variantName
);
if (!variantConf) {
return log(
'error',
`Unable to find a variant (${variantName}) within the system (${systemConf.name}). Please check your Emulsify project config and make sure the project.platform value is correct, or select a system with a variant that is compatible with the platform you are using.`,
EXIT_ERROR
);
}

try {
// Import all components in the list.
const componentsList = projectConfig.variant?.components || [];
const componentsWithDependencies = buildComponentDependencyList(
variantConf.components,
componentsList.map((component) => component.name)
);

for (const component of componentsWithDependencies) {
await installComponentFromCache(systemConf, variantConf, component, true);
}
} catch (e) {
return log(
'error',
`Unable to install system assets and/or required components: ${R.toString(
e
)}`,
EXIT_ERROR
);
}

return log(
'success',
`Successfully updated the ${systemConf.name}.`,
EXIT_SUCCESS
);
}
9 changes: 9 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import withProgressBar from './handlers/hofs/withProgressBar';
import init from './handlers/init';
import systemList from './handlers/systemList';
import systemInstall from './handlers/systemInstall';
import systemImport from './handlers/systemImport';
import componentList from './handlers/componentList';
import componentInstall from './handlers/componentInstall';

Expand Down Expand Up @@ -66,6 +67,10 @@ system
}
)
.action(systemInstall);
system
.command('import')
.description('Import components listed in project configuration file')
.action(systemImport);

// Component sub-commands.
const component = program
Expand All @@ -90,6 +95,10 @@ component
'-a --all',
'Use this to install all available components, rather than specifying a single component to install'
)
.option(
'-s --subtheme <subtheme>',
'This option use name as subtheme to pull group of components from full list of the components'
)
.alias('i')
.description(
"Install a component from within the current project's system and variant"
Expand Down
3 changes: 3 additions & 0 deletions src/schemas/emulsifyProjectConfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,9 @@
"structureImplementations": {
"$ref": "variant.json#/definitions/structureImplementations"
},
"components": {
"$ref": "variant.json#/definitions/components"
},
"repository": {
"type": "string",
"description": "Git repository containing the system this project is utilizing"
Expand Down
7 changes: 7 additions & 0 deletions src/schemas/variant.json
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,13 @@
"items": {
"type": "string"
}
},
"subtheme": {
"type": "array",
"description": "List of all subtheme to which this component belonning to and should be installed as part of subtheme",
"items": {
"type": "string"
}
}
},
"additionalProperties": false,
Expand Down
29 changes: 29 additions & 0 deletions src/types/_emulsifyProjectConfig.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,35 @@ export interface EmulsifyProjectConfiguration {
*/
directory: string;
}[];
/**
* Array containing objects that describe each component available within the variant
*/
components?: {
/**
* Name of the component. MUST correspond with the folder name containing the component
*/
name: string;
/**
* Name of the structure to which the component belongs. This, along with the name, will determine which folder the component will live in
*/
structure: string;
/**
* Text describing the intended purpose of the component
*/
description?: string;
/**
* Boolean indicating whether or not the component is required
*/
required?: boolean;
/**
* Array containing list of all components from which depends current conponent
*/
dependency?: string[];
/**
* List of all subtheme to which this component belonning to and should be installed as part of subtheme
*/
subtheme?: string[];
}[];
/**
* Git repository containing the system this project is utilizing
*/
Expand Down
4 changes: 4 additions & 0 deletions src/types/_system.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,10 @@ export interface EmulsifySystem {
* Array containing list of all components from which depends current conponent
*/
dependency?: string[];
/**
* List of all subtheme to which this component belonning to and should be installed as part of subtheme
*/
subtheme?: string[];
}[];
/**
* Array containing objects that define general directories. These directories should contain files and assets that do not belong in a structure folder (such as font files)
Expand Down
4 changes: 4 additions & 0 deletions src/types/_variant.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,10 @@ export type Components = {
* Array containing list of all components from which depends current conponent
*/
dependency?: string[];
/**
* List of all subtheme to which this component belonning to and should be installed as part of subtheme
*/
subtheme?: string[];
}[];
/**
* Array containing objects that define general directories. These directories should contain files and assets that do not belong in a structure folder (such as font files)
Expand Down
1 change: 1 addition & 0 deletions src/types/handlers.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,5 +19,6 @@ declare module '@emulsify-cli/handlers' {
export type InstallComponentHandlerOptions = {
force?: boolean;
all?: boolean;
subtheme: string;
};
}
Loading