From e51014c39a5446f16589ed83a32c8a8342ae8610 Mon Sep 17 00:00:00 2001 From: Joshua Wood Date: Sat, 13 Nov 2021 20:49:40 -0500 Subject: [PATCH 1/9] added capability to remove files in Gen that don't exist in src --- src/cli/src/cli/build.ts | 106 ++++++++++++++++++++++----------------- 1 file changed, 59 insertions(+), 47 deletions(-) diff --git a/src/cli/src/cli/build.ts b/src/cli/src/cli/build.ts index 94327075..5b6cac48 100644 --- a/src/cli/src/cli/build.ts +++ b/src/cli/src/cli/build.ts @@ -17,41 +17,26 @@ import { isStandardPage, isStaticPage, isStaticView, options, PageKind } from ". 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() ]) .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 => { + const toAction = async (filepath: string[]): Promise => { 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 => - 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( @@ -62,9 +47,30 @@ 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.generated + const generatedFiles = await relativePagePaths(genFilePath) + + const toAction = async (filepath: string): Promise => { + 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)) +} + + type FilepathSegments = { kind: PageKind, entry: PageEntry @@ -140,10 +146,10 @@ type PageEntry = { const getAllPageEntries = async (): Promise => { 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([ @@ -152,6 +158,12 @@ const getAllPageEntries = async (): Promise => { ]).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') @@ -176,28 +188,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[] @@ -225,11 +237,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) @@ -274,7 +286,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) { From e249e277c9c40f90b2d83e239961a663d24b23f0 Mon Sep 17 00:00:00 2001 From: Joshua Wood Date: Sat, 13 Nov 2021 20:55:44 -0500 Subject: [PATCH 2/9] returning promises where necessary --- src/cli/src/file.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/cli/src/file.ts b/src/cli/src/file.ts index 878cead0..a238f55e 100644 --- a/src/cli/src/file.ts +++ b/src/cli/src/file.ts @@ -25,20 +25,21 @@ export const remove = async (filepath: string) => { export const scan = async (dir: string, extension = '.elm'): Promise => { 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 => fs.readdir(dir) .then(data => data.map(p => path.join(dir, p))) + .catch(_ => []) const isDirectory = (dir: string): Promise => fs.lstat(dir).then(data => data.isDirectory()).catch(_ => false) From 6728bd12fc0d49ed4b99d0c65790e754493fcbe5 Mon Sep 17 00:00:00 2001 From: Joshua Wood Date: Sat, 13 Nov 2021 20:55:49 -0500 Subject: [PATCH 3/9] formatting --- src/cli/src/file.ts | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/cli/src/file.ts b/src/cli/src/file.ts index a238f55e..761c77ea 100644 --- a/src/cli/src/file.ts +++ b/src/cli/src/file.ts @@ -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' }) } @@ -57,15 +57,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)) ) @@ -74,18 +75,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 => +export const mkdir = (folder: string): Promise => fs.mkdir(folder, { recursive: true }) export const read = async (path: string) => From 67b52790e924e7f7604bb009fad1e8c5ee80ac97 Mon Sep 17 00:00:00 2001 From: Joshua Wood Date: Sat, 13 Nov 2021 20:56:58 -0500 Subject: [PATCH 4/9] added path to generated pages --- src/cli/src/config.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/cli/src/config.ts b/src/cli/src/config.ts index c9ded2c4..7e0768d2 100644 --- a/src/cli/src/config.ts +++ b/src/cli/src/config.ts @@ -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'), From 9495a207aa5920f7745c88f39a33a9f55c527ce5 Mon Sep 17 00:00:00 2001 From: Joshua Wood Date: Sat, 13 Nov 2021 20:59:11 -0500 Subject: [PATCH 5/9] changed formatting --- src/cli/src/templates/utils.ts | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/cli/src/templates/utils.ts b/src/cli/src/templates/utils.ts index 8e55cf87..0bc1973c 100644 --- a/src/cli/src/templates/utils.ts +++ b/src/cli/src/templates/utils.ts @@ -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') From b7466f7add6a790f14f5d10acf814526941ebd0e Mon Sep 17 00:00:00 2001 From: Joshua Wood Date: Sat, 13 Nov 2021 21:08:59 -0500 Subject: [PATCH 6/9] fixed generated directory path --- src/cli/src/cli/build.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/cli/src/cli/build.ts b/src/cli/src/cli/build.ts index 5b6cac48..90d56771 100644 --- a/src/cli/src/cli/build.ts +++ b/src/cli/src/cli/build.ts @@ -52,7 +52,7 @@ const createMissingDefaultFiles = async () => { } const removeUnusedGeneratedFiles = async () => { - const genFilePath = config.folders.generated + const genFilePath = config.folders.pages.generated const generatedFiles = await relativePagePaths(genFilePath) const toAction = async (filepath: string): Promise => { From 3348f7ed9b7e68ab9788c01f0b27b3803c5e7537 Mon Sep 17 00:00:00 2001 From: Joshua Wood Date: Sat, 13 Nov 2021 22:10:00 -0500 Subject: [PATCH 7/9] this should not catch... made a mistake --- src/cli/src/file.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/cli/src/file.ts b/src/cli/src/file.ts index 761c77ea..64e918e5 100644 --- a/src/cli/src/file.ts +++ b/src/cli/src/file.ts @@ -39,7 +39,6 @@ export const scan = async (dir: string, extension = '.elm'): Promise = const ls = (dir: string): Promise => fs.readdir(dir) .then(data => data.map(p => path.join(dir, p))) - .catch(_ => []) const isDirectory = (dir: string): Promise => fs.lstat(dir).then(data => data.isDirectory()).catch(_ => false) From c28c66685121c865b1cf50d6f49b226ce2ded865 Mon Sep 17 00:00:00 2001 From: Joshua Wood Date: Sat, 13 Nov 2021 23:16:38 -0500 Subject: [PATCH 8/9] added ability to remove empty directories --- src/cli/src/cli/build.ts | 13 ++++++++++++- src/cli/src/file.ts | 10 ++++++++++ 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/src/cli/src/cli/build.ts b/src/cli/src/cli/build.ts index 90d56771..d882f7e4 100644 --- a/src/cli/src/cli/build.ts +++ b/src/cli/src/cli/build.ts @@ -15,13 +15,15 @@ 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" +import { fileURLToPath } from "url" const elm = require('node-elm-compiler') export const build = ({ env, runElmMake }: { env: Environment, runElmMake: boolean }) => () => Promise.all([ createMissingDefaultFiles(), createMissingAddTemplates(), - removeUnusedGeneratedFiles() + removeUnusedGeneratedFiles(), + removeEmptyDirs() ]) .then(createGeneratedFiles) .then(runElmMake ? compileMainElm(env) : _ => ` ${check} ${bold}elm-spa${reset} generated new files.`) @@ -70,6 +72,15 @@ const removeUnusedGeneratedFiles = async () => { return await Promise.all(generatedFiles.map(toAction)) } +export const removeEmptyDirs = async (): Promise => { + 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 = { kind: PageKind, diff --git a/src/cli/src/file.ts b/src/cli/src/file.ts index 64e918e5..04ba8112 100644 --- a/src/cli/src/file.ts +++ b/src/cli/src/file.ts @@ -23,6 +23,16 @@ export const remove = async (filepath: string) => { : fs.rmdir(filepath, { recursive: true }) } +export const scanEmptyDirs = async (dir: string): Promise => { + 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 => { const doesExist = await exists(dir) if (!doesExist) return Promise.resolve([]) From 1ba5bcff2c11fe41623968e0c3ce68b7f521e187 Mon Sep 17 00:00:00 2001 From: Joshua Wood Date: Sun, 14 Nov 2021 01:22:51 -0500 Subject: [PATCH 9/9] removed accidental ts import --- src/cli/src/cli/build.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/cli/src/cli/build.ts b/src/cli/src/cli/build.ts index d882f7e4..df342a0f 100644 --- a/src/cli/src/cli/build.ts +++ b/src/cli/src/cli/build.ts @@ -15,7 +15,7 @@ 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" -import { fileURLToPath } from "url" + const elm = require('node-elm-compiler') export const build = ({ env, runElmMake }: { env: Environment, runElmMake: boolean }) => () =>