Skip to content

This PR cleans up the files in then generated folder when elm-spa gen is run #118

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

Open
wants to merge 10 commits into
base: main
Choose a base branch
from
117 changes: 70 additions & 47 deletions src/cli/src/cli/build.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,43 +15,30 @@ import terser from 'terser'
import { bold, underline, colors, reset, check, dim, dot, warn, error } from "../terminal"
import { isStandardPage, isStaticPage, isStaticView, options, PageKind } from "../templates/utils"
import { createMissingAddTemplates } from "./_common"

const elm = require('node-elm-compiler')

export const build = ({ env, runElmMake } : { env : Environment, runElmMake: boolean }) => () =>
export const build = ({ env, runElmMake }: { env: Environment, runElmMake: boolean }) => () =>
Promise.all([
createMissingDefaultFiles(),
createMissingAddTemplates()
createMissingAddTemplates(),
removeUnusedGeneratedFiles(),
removeEmptyDirs()
])
.then(createGeneratedFiles)
.then(runElmMake ? compileMainElm(env): _ => ` ${check} ${bold}elm-spa${reset} generated new files.`)
.then(runElmMake ? compileMainElm(env) : _ => ` ${check} ${bold}elm-spa${reset} generated new files.`)

const createMissingDefaultFiles = async () => {
type Action
= ['DELETE_FROM_DEFAULTS', string[]]
| ['CREATE_IN_DEFAULTS', string[]]
| ['DO_NOTHING', string[]]

const toAction = async (filepath: string[]): Promise<Action> => {
const toAction = async (filepath: string[]): Promise<any> => {
const [inDefaults, inSrc] = await Promise.all([
exists(path.join(config.folders.defaults.dest, ...filepath)),
exists(path.join(config.folders.src, ...filepath))
])

if (inSrc && inDefaults) {
return ['DELETE_FROM_DEFAULTS', filepath]
} else if (!inSrc) {
return ['CREATE_IN_DEFAULTS', filepath]
} else {
return ['DO_NOTHING', filepath]
}
}

const actions = await Promise.all(config.defaults.map(toAction))

const performDefaultFileAction = ([action, relative]: Action): Promise<any> =>
action === 'CREATE_IN_DEFAULTS' ? createDefaultFile(relative)
: action === 'DELETE_FROM_DEFAULTS' ? deleteFromDefaults(relative)
return inSrc && inDefaults ? deleteFromDefaults(filepath)
: !inSrc ? createDefaultFile(filepath)
: Promise.resolve()
}

const createDefaultFile = async (relative: string[]) =>
File.copyFile(
Expand All @@ -62,7 +49,37 @@ const createMissingDefaultFiles = async () => {
const deleteFromDefaults = async (relative: string[]) =>
File.remove(path.join(config.folders.defaults.dest, ...relative))

return Promise.all(actions.map(performDefaultFileAction))
return await Promise.all(config.defaults.map(toAction))

}

const removeUnusedGeneratedFiles = async () => {
const genFilePath = config.folders.pages.generated
const generatedFiles = await relativePagePaths(genFilePath)

const toAction = async (filepath: string): Promise<any> => {
const [inSrc, inDefaults] = await Promise.all([
exists(path.join(config.folders.defaults.src, filepath)),
exists(path.join(genFilePath, filepath))
]);

return !inSrc && !inDefaults ? deleteFromGenerated(filepath) : Promise.resolve()
}

const deleteFromGenerated = async (relative: string) =>
File.remove(path.join(genFilePath, relative))

return await Promise.all(generatedFiles.map(toAction))
}

export const removeEmptyDirs = async (): Promise<void> => {
const scanEmptyPageDirsIn = async (folder: string) =>
File.scanEmptyDirs(folder)

const emptyDirsInGen = await scanEmptyPageDirsIn(config.folders.pages.generated)
if (!emptyDirsInGen.length) return Promise.resolve()
await Promise.all(emptyDirsInGen.map(File.remove))
return Promise.resolve(removeEmptyDirs())
}

type FilepathSegments = {
Expand Down Expand Up @@ -140,10 +157,10 @@ type PageEntry = {
const getAllPageEntries = async (): Promise<PageEntry[]> => {
const scanPageFilesIn = async (folder: string) => {
const items = await File.scan(folder)
return items.map(s => ({
return Promise.resolve(items.map(s => ({
filepath: s,
segments: s.substring(folder.length + 1, s.length - '.elm'.length).split(path.sep)
}))
})))
}

return Promise.all([
Expand All @@ -152,6 +169,12 @@ const getAllPageEntries = async (): Promise<PageEntry[]> => {
]).then(([left, right]) => left.concat(right))
}

const relativePagePaths = async (folder: string) => {
const items = await File.scan(folder)
return Promise.resolve(items.map(s => s.substring(folder.length, s.length)))
}


type Environment = 'production' | 'development'

const outputFilepath = path.join(config.folders.dist, 'elm.js')
Expand All @@ -176,28 +199,28 @@ const compileMainElm = (env: Environment) => async () => {
debug: inDevelopment,
optimize: inProduction,
})
.catch((error: Error) => {
try { return colorElmError(JSON.parse(error.message.split('\n')[1])) }
catch {
const { RED, green } = colors
return Promise.reject([
`${RED}!${reset} elm-spa failed to understand an error`,
`Please report the output below to ${green}https://github.com/ryannhg/elm-spa/issues${reset}`,
`-----`,
JSON.stringify(error, null, 2),
`-----`,
`${RED}!${reset} elm-spa failed to understand an error`,
`Please send the output above to ${green}https://github.com/ryannhg/elm-spa/issues${reset}`,
``
].join('\n\n'))
}
})
.catch((error: Error) => {
try { return colorElmError(JSON.parse(error.message.split('\n')[1])) }
catch {
const { RED, green } = colors
return Promise.reject([
`${RED}!${reset} elm-spa failed to understand an error`,
`Please report the output below to ${green}https://github.com/ryannhg/elm-spa/issues${reset}`,
`-----`,
JSON.stringify(error, null, 2),
`-----`,
`${RED}!${reset} elm-spa failed to understand an error`,
`Please send the output above to ${green}https://github.com/ryannhg/elm-spa/issues${reset}`,
``
].join('\n\n'))
}
})
}

type ElmError
= ElmCompileError
| ElmJsonError

type ElmCompileError = {
type: 'compile-errors'
errors: ElmProblemError[]
Expand Down Expand Up @@ -225,11 +248,11 @@ const compileMainElm = (env: Environment) => async () => {
string: string
}

const colorElmError = (output : ElmError) => {
const errors : ElmProblemError[] =
const colorElmError = (output: ElmError) => {
const errors: ElmProblemError[] =
output.type === 'compile-errors'
? output.errors
: [ { path: output.path, problems: [output] } ]
: [{ path: output.path, problems: [output] }]

const strIf = (str: string) => (cond: boolean): string => cond ? str : ''
const boldIf = strIf(bold)
Expand Down Expand Up @@ -274,7 +297,7 @@ const compileMainElm = (env: Environment) => async () => {
.then(_ => [success() + '\n'])
}

const ensureElmIsInstalled = async (environment : Environment) => {
const ensureElmIsInstalled = async (environment: Environment) => {
await new Promise((resolve, reject) => {
ChildProcess.exec('elm', (err) => {
if (err) {
Expand Down
3 changes: 2 additions & 1 deletion src/cli/src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@ const config = {
src: path.join(cwd, 'src'),
pages: {
src: path.join(cwd, 'src', 'Pages'),
defaults: path.join(cwd, '.elm-spa', 'defaults', 'Pages')
defaults: path.join(cwd, '.elm-spa', 'defaults', 'Pages'),
generated: path.join(cwd, '.elm-spa', 'generated', 'Gen', 'Params')
},
defaults: {
src: path.join(root, 'src', 'defaults'),
Expand Down
29 changes: 20 additions & 9 deletions src/cli/src/file.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import path from "path"
* @param filepath - the absolute path of the file to create
* @param contents - the raw string contents of the file
*/
export const create = async (filepath : string, contents : string) => {
export const create = async (filepath: string, contents: string) => {
await ensureFolderExists(filepath)
return fs.writeFile(filepath, contents, { encoding: 'utf8' })
}
Expand All @@ -23,17 +23,27 @@ export const remove = async (filepath: string) => {
: fs.rmdir(filepath, { recursive: true })
}

export const scanEmptyDirs = async (dir: string): Promise<string[]> => {
const doesExist = await exists(dir)
if (!doesExist) return Promise.resolve([])
const items = await ls(dir)
if (!items.length) return Promise.resolve([dir])
const dirs = await keepFolders(items)
const nestedEmptyDirs = await Promise.all(dirs.map(f => scanEmptyDirs(f)))
return Promise.resolve(nestedEmptyDirs.reduce((a, b) => a.concat(b), []))
}

export const scan = async (dir: string, extension = '.elm'): Promise<string[]> => {
const doesExist = await exists(dir)
if (!doesExist) return []
if (!doesExist) return Promise.resolve([])
const items = await ls(dir)
const [folders, files] = await Promise.all([
keepFolders(items),
items.filter(f => f.endsWith(extension))
Promise.resolve(items.filter(f => f.endsWith(extension)))
])
const listOfFiles = await Promise.all(folders.map(f => scan(f, extension)))
const nestedFiles = listOfFiles.reduce((a, b) => a.concat(b), [])
return files.concat(nestedFiles)
return Promise.resolve(files.concat(nestedFiles))
}

const ls = (dir: string): Promise<string[]> =>
Expand All @@ -56,15 +66,16 @@ export const exists = (filepath: string) =>
.catch(_ => false)



/**
* Copy the file or folder at the given path.
* @param filepath - the path of the file or folder to copy
*/
export const copy = (src : string, dest : string) => {
export const copy = (src: string, dest: string) => {
const exists = oldFs.existsSync(src)
const stats = exists && oldFs.statSync(src)
if (stats && stats.isDirectory()) {
try { oldFs.mkdirSync(dest, { recursive: true }) } catch (_) {}
try { oldFs.mkdirSync(dest, { recursive: true }) } catch (_) { }
oldFs.readdirSync(src).forEach(child =>
copy(path.join(src, child), path.join(dest, child))
)
Expand All @@ -73,18 +84,18 @@ export const copy = (src : string, dest : string) => {
}
}

export const copyFile = async (src : string, dest : string) => {
export const copyFile = async (src: string, dest: string) => {
await ensureFolderExists(dest)
return fs.copyFile(src, dest)
}


const ensureFolderExists = async (filepath : string) => {
const ensureFolderExists = async (filepath: string) => {
const folder = filepath.split(path.sep).slice(0, -1).join(path.sep)
return fs.mkdir(folder, { recursive: true })
}

export const mkdir = (folder : string) : Promise<string> =>
export const mkdir = (folder: string): Promise<string> =>
fs.mkdir(folder, { recursive: true })

export const read = async (path: string) =>
Expand Down
7 changes: 2 additions & 5 deletions src/cli/src/templates/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -319,11 +319,8 @@ const pageModelArguments = (path: string[], options : Options) : string => {
const exposes = (value : string) => (str : string) : boolean => {
const regex = new RegExp('^module\\s+[^\\s]+\\s+exposing\\s+\\(((?:\\.\\)|[^)])+)\\)')
const match = (str.match(regex) || [])[1]
if (match) {
return match.split(',').filter(a => a).map(a => a.trim()).includes(value)
} else {
return false
}
return match ? match.split(',').filter(a => a).map(a => a.trim()).includes(value)
: false
}

export const exposesModel = exposes('Model')
Expand Down