-
Notifications
You must be signed in to change notification settings - Fork 3.5k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Migrate custom v3 config to local plugins if needed
- Loading branch information
1 parent
f494781
commit 2981853
Showing
8 changed files
with
403 additions
and
77 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,187 @@ | ||
import {parse, prettyPrint} from 'recast'; | ||
import {builders, namedTypes} from 'ast-types'; | ||
import * as babelParser from 'recast/parsers/babel'; | ||
import {copy, copySync, existsSync, readFileSync, writeFileSync} from 'fs-extra'; | ||
import {dirname, resolve} from 'path'; | ||
import _ from 'lodash'; | ||
|
||
import notify from '../notify'; | ||
import {_extractDefault} from './init'; | ||
import {cfgDir, cfgPath, defaultCfg, legacyCfgPath, plugs, schemaFile, schemaPath} from './paths'; | ||
|
||
// function to remove all json serializable entries from an array expression | ||
function removeElements(node: namedTypes.ArrayExpression): namedTypes.ArrayExpression { | ||
const newElements = node.elements.filter((element) => { | ||
if (namedTypes.ObjectExpression.check(element)) { | ||
const newElement = removeProperties(element); | ||
if (newElement.properties.length === 0) { | ||
return false; | ||
} | ||
} else if (namedTypes.ArrayExpression.check(element)) { | ||
const newElement = removeElements(element); | ||
if (newElement.elements.length === 0) { | ||
return false; | ||
} | ||
} else if (namedTypes.Literal.check(element)) { | ||
return false; | ||
} | ||
return true; | ||
}); | ||
return {...node, elements: newElements}; | ||
} | ||
|
||
// function to remove all json serializable properties from an object expression | ||
function removeProperties(node: namedTypes.ObjectExpression): namedTypes.ObjectExpression { | ||
const newProperties = node.properties.filter((property) => { | ||
if ( | ||
namedTypes.ObjectProperty.check(property) && | ||
(namedTypes.Literal.check(property.key) || namedTypes.Identifier.check(property.key)) && | ||
!property.computed | ||
) { | ||
if (namedTypes.ObjectExpression.check(property.value)) { | ||
const newValue = removeProperties(property.value); | ||
if (newValue.properties.length === 0) { | ||
return false; | ||
} | ||
} else if (namedTypes.ArrayExpression.check(property.value)) { | ||
const newValue = removeElements(property.value); | ||
if (newValue.elements.length === 0) { | ||
return false; | ||
} | ||
} else if (namedTypes.Literal.check(property.value)) { | ||
return false; | ||
} | ||
} | ||
return true; | ||
}); | ||
return {...node, properties: newProperties}; | ||
} | ||
|
||
export function configToPlugin(code: string): string { | ||
const ast: namedTypes.File = parse(code, { | ||
parser: babelParser | ||
}); | ||
const statements = ast.program.body; | ||
let moduleExportsNode: namedTypes.AssignmentExpression | null = null; | ||
let configNode: any = null; | ||
|
||
for (const statement of statements) { | ||
if (namedTypes.ExpressionStatement.check(statement)) { | ||
const expression = statement.expression; | ||
if ( | ||
namedTypes.AssignmentExpression.check(expression) && | ||
expression.operator === '=' && | ||
namedTypes.MemberExpression.check(expression.left) && | ||
namedTypes.Identifier.check(expression.left.object) && | ||
expression.left.object.name === 'module' && | ||
namedTypes.Identifier.check(expression.left.property) && | ||
expression.left.property.name === 'exports' | ||
) { | ||
moduleExportsNode = expression; | ||
if (namedTypes.ObjectExpression.check(expression.right)) { | ||
const properties = expression.right.properties; | ||
for (const property of properties) { | ||
if ( | ||
namedTypes.ObjectProperty.check(property) && | ||
namedTypes.Identifier.check(property.key) && | ||
property.key.name === 'config' | ||
) { | ||
configNode = property.value; | ||
if (namedTypes.ObjectExpression.check(property.value)) { | ||
configNode = removeProperties(property.value); | ||
} | ||
} | ||
} | ||
} else { | ||
configNode = builders.memberExpression(moduleExportsNode.right, builders.identifier('config')); | ||
} | ||
} | ||
} | ||
} | ||
|
||
if (!moduleExportsNode) { | ||
console.log('No module.exports found in config'); | ||
return ''; | ||
} | ||
if (!configNode) { | ||
console.log('No config field found in module.exports'); | ||
return ''; | ||
} | ||
if (namedTypes.ObjectExpression.check(configNode) && configNode.properties.length === 0) { | ||
return ''; | ||
} | ||
|
||
moduleExportsNode.right = builders.objectExpression([ | ||
builders.property( | ||
'init', | ||
builders.identifier('decorateConfig'), | ||
builders.arrowFunctionExpression( | ||
[builders.identifier('_config')], | ||
builders.callExpression( | ||
builders.memberExpression(builders.identifier('Object'), builders.identifier('assign')), | ||
[builders.objectExpression([]), builders.identifier('_config'), configNode] | ||
) | ||
) | ||
) | ||
]); | ||
|
||
return prettyPrint(ast, {tabWidth: 2}).code; | ||
} | ||
|
||
export const _write = (path: string, data: string) => { | ||
// This method will take text formatted as Unix line endings and transform it | ||
// to text formatted with DOS line endings. We do this because the default | ||
// text editor on Windows (notepad) doesn't Deal with LF files. Still. In 2017. | ||
const crlfify = (str: string) => { | ||
return str.replace(/\r?\n/g, '\r\n'); | ||
}; | ||
const format = process.platform === 'win32' ? crlfify(data.toString()) : data; | ||
writeFileSync(path, format, 'utf8'); | ||
}; | ||
|
||
// Migrate Hyper3 config to Hyper4 but only if the user hasn't manually | ||
// touched the new config and if the old config is not a symlink | ||
export const migrateHyper3Config = () => { | ||
copy(schemaPath, resolve(cfgDir, schemaFile), (err) => { | ||
if (err) { | ||
console.error(err); | ||
} | ||
}); | ||
|
||
if (existsSync(cfgPath)) { | ||
return; | ||
} | ||
|
||
if (!existsSync(legacyCfgPath)) { | ||
copySync(defaultCfg, cfgPath); | ||
return; | ||
} | ||
|
||
// Migrate | ||
copySync(resolve(dirname(legacyCfgPath), '.hyper_plugins', 'local'), plugs.local); | ||
|
||
const defaultCfgData = JSON.parse(readFileSync(defaultCfg, 'utf8')); | ||
let newCfgData; | ||
try { | ||
const legacyCfgRaw = readFileSync(legacyCfgPath, 'utf8'); | ||
const legacyCfgData = _extractDefault(legacyCfgRaw); | ||
newCfgData = _.merge({}, defaultCfgData, legacyCfgData); | ||
|
||
const pluginCode = configToPlugin(legacyCfgRaw); | ||
if (pluginCode) { | ||
const pluginPath = resolve(plugs.local, 'migrated-hyper3-config.js'); | ||
newCfgData.localPlugins = ['migrated-hyper3-config', ...(newCfgData.localPlugins || [])]; | ||
_write(pluginPath, pluginCode); | ||
} | ||
} catch (e) { | ||
console.error(e); | ||
notify( | ||
'Hyper 4', | ||
`Failed to migrate your config from Hyper 3.\nDefault config will be created instead at ${cfgPath}` | ||
); | ||
newCfgData = defaultCfgData; | ||
} | ||
_write(cfgPath, JSON.stringify(newCfgData, null, 2)); | ||
|
||
notify('Hyper 4', `Settings location and format has changed to ${cfgPath}`); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.