diff --git a/.prettierrc b/.prettierrc deleted file mode 100644 index 528e3605..00000000 --- a/.prettierrc +++ /dev/null @@ -1,5 +0,0 @@ -{ - "semi": false, - "trailingComma": "none", - "singleQuote": true -} diff --git a/package.json b/package.json index e0fe9b5b..330431b6 100644 --- a/package.json +++ b/package.json @@ -41,6 +41,12 @@ "frourio", "typescript" ], + "prettier": { + "printWidth": 100, + "semi": false, + "trailingComma": "none", + "singleQuote": true + }, "dependencies": { "@aspida/axios": "^1.11.0", "@aspida/swr": "^1.11.0", diff --git a/scripts/build-file.ts b/scripts/build-file.ts index 6fa27307..b6198d87 100644 --- a/scripts/build-file.ts +++ b/scripts/build-file.ts @@ -17,12 +17,7 @@ interface Params { const omitImportNodeNSPlugin: Plugin = { name: 'omit-import-node-ns', setup(build) { - build.onResolve({ filter: /^node:/ }, (args) => { - return { - path: args.path.slice(5), - external: true - } - }) + build.onResolve({ filter: /^node:/ }, (args) => ({ path: args.path.slice(5), external: true })) } } @@ -50,20 +45,13 @@ const main = async ({ from, to, watch, prod, target, server }: Params) => { minify: true, keepNames: true, jsx: server ? undefined : 'transform', - define: { - 'process.env.NODE_ENV': prod ? '"production"' : '"development"' - }, + define: { 'process.env.NODE_ENV': prod ? '"production"' : '"development"' }, sourcemap: 'inline', bundle: true, outfile: toPath, entryPoints: [fromPath], watch: watchOptions, - plugins: [ - omitImportNodeNSPlugin, - nodeExternalsPlugin({ - allowList: [] - }) - ] + plugins: [omitImportNodeNSPlugin, nodeExternalsPlugin({ allowList: [] })] }) } diff --git a/server/api/answers/controller.ts b/server/api/answers/controller.ts index 95432159..7beec7dc 100644 --- a/server/api/answers/controller.ts +++ b/server/api/answers/controller.ts @@ -22,9 +22,7 @@ export default defineController(({ appendLogging, clientReady }) => ({ for (let i = 0; i < 600; i++) { await new Promise((resolve) => setTimeout(resolve, 1000)) try { - const client = axios.create({ - baseURL: `http://localhost:${serverPort}/api` - }) + const client = axios.create({ baseURL: `http://localhost:${serverPort}/api` }) const res = await client.get('tasks') if (res.status === 200) { clientReady() diff --git a/server/api/dbConnection/controller.ts b/server/api/dbConnection/controller.ts index adaba01d..9c92f3c9 100644 --- a/server/api/dbConnection/controller.ts +++ b/server/api/dbConnection/controller.ts @@ -16,16 +16,9 @@ export default defineController(() => ({ return { status: 200, body: { enabled: true } } } - const templateCtx = answersToTemplateContext({ - ...answers, - serverPort: 0, - clientPort: 0 - }) + const templateCtx = answersToTemplateContext({ ...answers, serverPort: 0, clientPort: 0 }) - if ( - templateCtx.dbHost !== '127.0.0.1' && - templateCtx.dbHost !== 'localhost' - ) { + if (templateCtx.dbHost !== '127.0.0.1' && templateCtx.dbHost !== 'localhost') { return { status: 200, body: { @@ -44,10 +37,7 @@ export default defineController(() => ({ } if (answers.db === 'mysql') { - const conn = await mariadb.createConnection({ - ...config, - allowPublicKeyRetrieval: true - }) + const conn = await mariadb.createConnection({ ...config, allowPublicKeyRetrieval: true }) await conn.query(`CREATE DATABASE IF NOT EXISTS ${templateCtx.dbName}`) @@ -67,10 +57,7 @@ export default defineController(() => ({ return { status: 200, body: { enabled: true } } } catch (e) { - return { - status: 200, - body: { enabled: false, err: e instanceof Error ? e.message : '' } - } + return { status: 200, body: { enabled: false, err: e instanceof Error ? e.message : '' } } } } })) diff --git a/server/api/localPath/controller.ts b/server/api/localPath/controller.ts index db7c4eae..04237fa8 100644 --- a/server/api/localPath/controller.ts +++ b/server/api/localPath/controller.ts @@ -5,13 +5,8 @@ import path from 'path' export default defineController(() => ({ post: async ({ body: { path: relativePath } }) => { const absPath = path.resolve(process.cwd(), relativePath) - const body = { - absPath, - canContinue: canContinueOnPath(await getPathStatus(absPath)) - } - return { - status: 200, - body - } + const body = { absPath, canContinue: canContinueOnPath(await getPathStatus(absPath)) } + + return { status: 200, body } } })) diff --git a/server/common/prompts.ts b/server/common/prompts.ts index a8c460b2..b8fd3fbd 100644 --- a/server/common/prompts.ts +++ b/server/common/prompts.ts @@ -226,17 +226,11 @@ export const cfaPrompts: Prompt[] = [ name: `${db}Db${what}` as PromptName, message: (ans: Answers) => { if (ans.orm === 'prisma') { - return `dev DB ${typeormEnv}: server/prisma/.env API_DATABASE_URL (${getPrismaDbUrl( - { - ...ans, - [`${db}Db${what}`]: (ans as any)[`${db}Db${what}`] || 'HERE', - [`${db}DbPass`]: (ans as any)[`${db}DbPass`] - ? '***' - : what === 'Pass' - ? 'HERE' - : '' - } - )}) =` + return `dev DB ${typeormEnv}: server/prisma/.env API_DATABASE_URL (${getPrismaDbUrl({ + ...ans, + [`${db}Db${what}`]: (ans as any)[`${db}Db${what}`] || 'HERE', + [`${db}DbPass`]: (ans as any)[`${db}DbPass`] ? '***' : what === 'Pass' ? 'HERE' : '' + })}) =` } else { return `dev DB: server/.env TYEPORM_${typeormEnv} =` } @@ -345,8 +339,7 @@ export const cfaPrompts: Prompt[] = [ `- **API_DATABASE_URL**: ${ ( { - sqlite: - 'Production URL for SQLite. e.g. `file:///mnt/efs-data/db.sqlite`', + sqlite: 'Production URL for SQLite. e.g. `file:///mnt/efs-data/db.sqlite`', mysql: 'Production URL for MySQL. e.g. `mysql://mysql-instance1.123456789012.us-east-1.rds.amazonaws.com:3306`', postgresql: @@ -456,9 +449,7 @@ export const cfaPrompts: Prompt[] = [ '### Deploy to Vercel', '', '1. Visit [vercel.com](https://vercel.com) and create new project.', - '2. Set **BUILD COMMAND** to `' + - ans.pm + - ' run build:client`.', + '2. Set **BUILD COMMAND** to `' + ans.pm + ' run build:client`.', '3. Add environment variables **API_BASE_PATH** and **API_ORIGIN**.' ].join('\n') } @@ -478,9 +469,7 @@ export const cfaPrompts: Prompt[] = [ '1. Visit [app.netlify.com](https://app.netlify.com) and create new project.', '2. Go to **Site Settings** > **Build & Deploy**', ' a. Set **Repository** to your remote repository', - ' b. Set **Build command** to `' + - ans.pm + - ' run build:client`', + ' b. Set **Build command** to `' + ans.pm + ' run build:client`', ' c. Set **Publish directory** to `out/`', '3. Go to **Site Settings** > **Build & Deploy** > **Environment**', ' a. Add environment variables **API_ORIGIN** and **API_BASE_PATH**.' @@ -550,15 +539,10 @@ export const cfaPrompts: Prompt[] = [ ] /* eslint-disable @typescript-eslint/no-explicit-any */ -export const calculatePrompts = ( - answers: Answers, - target: any = cfaPrompts -): any => { +export const calculatePrompts = (answers: Answers, target: any = cfaPrompts): any => { if (target === null) return target if (Array.isArray(target)) { - return target - .map((e: any) => calculatePrompts(answers, e)) - .filter((e: any) => e !== undefined) + return target.map((e: any) => calculatePrompts(answers, e)).filter((e: any) => e !== undefined) } if (typeof target === 'object') { const res: any = {} @@ -635,10 +619,7 @@ export const isAnswersValid = (answers: Answers) => { if (valid !== undefined) return valid if (el.type === 'list') { - return ( - (el.choices.filter((choice) => choice.value === value)[0]?.disabled ?? - false) === false - ) + return (el.choices.filter((choice) => choice.value === value)[0]?.disabled ?? false) === false } if (el.type === 'input') { diff --git a/server/common/template-context.ts b/server/common/template-context.ts index f6784bb3..88fe0999 100644 --- a/server/common/template-context.ts +++ b/server/common/template-context.ts @@ -1,9 +1,4 @@ -import { - Answers, - CommonDbInfo, - getCommonDbInfo, - getPrismaDbUrl -} from './prompts' +import { Answers, CommonDbInfo, getCommonDbInfo, getPrismaDbUrl } from './prompts' // This is something like `computed value`. // Additional keys should not include the same key on Answers. diff --git a/server/index.ts b/server/index.ts index c0c2f3e6..e59688c7 100644 --- a/server/index.ts +++ b/server/index.ts @@ -61,26 +61,17 @@ const basePath = '/api' const fastify = Fastify() // eslint-disable-next-line @typescript-eslint/no-explicit-any - fastify.register(FastifyStatic, { - root: path.join(__dirname, '../../out') - }) - await fastify.register(FastifyInject, { - dir, - logging, - ready - }) + fastify.register(FastifyStatic, { root: path.join(__dirname, '../../out') }) + await fastify.register(FastifyInject, { dir, logging, ready }) if (process.env.NODE_ENV === 'development') { fastify // eslint-disable-next-line @typescript-eslint/no-var-requires .register(require('@fastify/nextjs'), { dev: true, - conf: { - env: { - NEXT_PUBLIC_SERVER_PORT: port - } - } + conf: { env: { NEXT_PUBLIC_SERVER_PORT: port } } }) .after(() => { + // eslint-disable-next-line @typescript-eslint/no-explicit-any ;(fastify as any).next('/') }) } diff --git a/server/plugins/fastify-inject.ts b/server/plugins/fastify-inject.ts index 4b4448de..5be505c1 100644 --- a/server/plugins/fastify-inject.ts +++ b/server/plugins/fastify-inject.ts @@ -20,12 +20,9 @@ export interface FastifyInjectOptions { } const fastifyInject: FastifyPluginCallback = fp( - ( - fastify: FastifyInstance, - options: FastifyInjectOptions, - next: () => void - ) => { + (fastify: FastifyInstance, options: FastifyInjectOptions, next: () => void) => { fastify.decorateRequest('dir', { getter: () => options.dir }) + // eslint-disable-next-line @typescript-eslint/no-explicit-any fastify.decorate('appendLogging', (data: any) => { options.logging.push(data) }) @@ -34,10 +31,7 @@ const fastifyInject: FastifyPluginCallback = fp( }) next() }, - { - fastify: '>=3', - name: 'fastify-winston-logger' - } + { fastify: '>=3', name: 'fastify-winston-logger' } ) export default fastifyInject diff --git a/server/service/__tests__/package-json.spec.ts b/server/service/__tests__/package-json.spec.ts index db7608ea..1a9d9fb8 100644 --- a/server/service/__tests__/package-json.spec.ts +++ b/server/service/__tests__/package-json.spec.ts @@ -1,23 +1,14 @@ import { convertListToJson } from '../package-json' describe('convertListToJson', () => { - const deps = { - frourio: 'v1.0.0', - aspida: 'v1.0.0', - '@types/node': 'v1.0.0' - } + const deps = { frourio: 'v1.0.0', aspida: 'v1.0.0', '@types/node': 'v1.0.0' } + it('[frourio], indent=" "', () => { - expect(convertListToJson(deps, ['frourio'], ' ')).toMatch( - /^ {2}"frourio": "v.*"$/ - ) + expect(convertListToJson(deps, ['frourio'], ' ')).toMatch(/^ {2}"frourio": "v.*"$/) }) it('[frourio, aspida, frourio, frourio, @types/node], indent=" "', () => { expect( - convertListToJson( - deps, - ['frourio', 'aspida', 'frourio', 'frourio', '@types/node'], - ' ' - ) + convertListToJson(deps, ['frourio', 'aspida', 'frourio', 'frourio', '@types/node'], ' ') ).toMatch(/^ "@types\/node": "v.*",\n "aspida": "v.*",\n "frourio": "v.*"$/) }) }) diff --git a/server/service/answers.ts b/server/service/answers.ts index 498a4607..c4970aa7 100644 --- a/server/service/answers.ts +++ b/server/service/answers.ts @@ -40,12 +40,7 @@ type AnswersVer2 = Omit< | 'serverSourcePath' | 'designatedServer' > & - Partial< - Record< - 'dbHost' | 'dbUser' | 'dbPass' | 'dbUser' | 'dbPort' | 'dbFile', - string - > - > + Partial> type AnswersVer1 = Omit & { front?: string } type Schemas = [ { ver: 1; answers: AnswersVer1 }, @@ -88,10 +83,7 @@ export const cliMigration = [ { when: (answers: Schemas[number]['answers']) => 'front' in answers, warn: () => 'Use "client" instead of "front".', - handler: ({ - front, - ...others - }: Schemas[0]['answers']): Schemas[1]['answers'] => ({ + handler: ({ front, ...others }: Schemas[0]['answers']): Schemas[1]['answers'] => ({ ...others, client: front }) @@ -100,11 +92,7 @@ export const cliMigration = [ when: (answers: Schemas[number]['answers']) => key in answers, warn: (answers: Schemas[number]['answers']) => `Use "${answers.db}${capitailze(key)}" instead of "${key}".`, - handler: ({ - [key]: val, - db, - ...others - }: Schemas[0]['answers']): Schemas[1]['answers'] => ({ + handler: ({ [key]: val, db, ...others }: Schemas[0]['answers']): Schemas[1]['answers'] => ({ ...others, db, [`${db}${capitailze(key)}`]: val @@ -113,10 +101,7 @@ export const cliMigration = [ { when: (answers: Schemas[number]['answers']) => 'dbFile' in answers, warn: () => `Use "sqliteDbFile" instead of "dbFile".`, - handler: ({ - dbFile, - ...others - }: Schemas[1]['answers']): Schemas[2]['answers'] => ({ + handler: ({ dbFile, ...others }: Schemas[1]['answers']): Schemas[2]['answers'] => ({ ...others, sqliteDbFile: dbFile }) @@ -151,17 +136,12 @@ const installApp = async (answers: Answers, s: stream.Writable) => { const dir = allAnswers.dir ?? '' await generate( - { - ...allAnswers, - clientPort: await getClientPort(), - serverPort: await getServerPort() - }, + { ...allAnswers, clientPort: await getClientPort(), serverPort: await getServerPort() }, path.resolve(__dirname, '..') ) await completed(allAnswers, s) const npmClientPath = await realExecutablePath(answers.pm ?? 'npm') - const npmRun = (script: string) => new Promise((resolve, reject) => { const proc = spawn(npmClientPath, ['run', '--color', script], { @@ -200,10 +180,7 @@ const installApp = async (answers: Answers, s: stream.Writable) => { await fs.promises.writeFile(dbPath, JSON.stringify(db), 'utf8') } -export const getAnswers = (dir: string) => ({ - dir, - ...db.answers -}) +export const getAnswers = (dir: string) => ({ dir, ...db.answers }) export const updateAnswers = async (answers: Answers, s: stream.Writable) => { db = { @@ -216,12 +193,11 @@ export const updateAnswers = async (answers: Answers, s: stream.Writable) => { } } - const canContinue = canContinueOnPath( - await getPathStatus(path.resolve(process.cwd(), answers.dir || '')) + const canContinue = await getPathStatus(path.resolve(process.cwd(), answers.dir || '')).then( + canContinueOnPath ) - if (canContinue !== null) { - throw new Error(canContinue) - } + + if (canContinue !== null) throw new Error(canContinue) if (!fs.existsSync(dirPath)) await fs.promises.mkdir(dirPath) diff --git a/server/service/completed.ts b/server/service/completed.ts index f2a7f6eb..f465fc95 100644 --- a/server/service/completed.ts +++ b/server/service/completed.ts @@ -7,11 +7,7 @@ import stream from 'stream' import fs from 'fs' import realExecutablePath from 'real-executable-path' -export const npmInstall = async ( - cwd: string, - npmClient: string, - s: stream.Writable -) => { +export const npmInstall = async (cwd: string, npmClient: string, s: stream.Writable) => { const npmClientPath = await realExecutablePath(npmClient) await new Promise((resolve, reject) => { const proc = spawn(npmClientPath, ['install'], { @@ -40,10 +36,7 @@ export const completed = async (answers: Answers, s: stream.Writable) => { const gitCliPath = await realExecutablePath('git') await new Promise((resolve, reject) => { - const proc = spawn(gitCliPath, ['init'], { - stdio: ['inherit', 'pipe', 'pipe'], - cwd: outDir - }) + const proc = spawn(gitCliPath, ['init'], { stdio: ['inherit', 'pipe', 'pipe'], cwd: outDir }) proc.stdio[1]?.on('data', s.write.bind(s)) proc.stdio[2]?.on('data', s.write.bind(s)) proc.once('exit', resolve) diff --git a/server/service/generate.ts b/server/service/generate.ts index e68bed63..f0b03f93 100644 --- a/server/service/generate.ts +++ b/server/service/generate.ts @@ -4,16 +4,8 @@ import ejs from 'ejs' import isBinaryPath from 'is-binary-path' import { addAllUndefined, Answers, removeUnnecessary } from '$/common/prompts' import assert from 'assert' -import { - convertListToJson, - DepKeys, - getPackageVersions, - isDepKey -} from './package-json' -import { - TemplateContext, - answersToTemplateContext -} from '$/common/template-context' +import { convertListToJson, DepKeys, getPackageVersions, isDepKey } from './package-json' +import { TemplateContext, answersToTemplateContext } from '$/common/template-context' export const generate = async ( answers: Answers & { clientPort: number; serverPort: number }, @@ -58,10 +50,7 @@ export const generate = async ( try { const output = noEjs ? content - : ejs.render( - content.toString('utf-8').replace(/\r/g, ''), - templateContext - ) + : ejs.render(content.toString('utf-8').replace(/\r/g, ''), templateContext) await fs.promises.writeFile(path.resolve(nowOut, p), output) } catch (e: unknown) { console.error(e) @@ -100,32 +89,17 @@ export const generate = async ( await walk(path.resolve(rootDir, 'templates'), dir) const versions = getPackageVersions() - const setupPackageJson = async ( - rel: string, - depKey: DepKeys, - devDepKey: DepKeys - ) => { + const setupPackageJson = async (rel: string, depKey: DepKeys, devDepKey: DepKeys) => { const packageJsonPath = path.resolve(dir, rel) const packageJson = (await fs.promises.readFile(packageJsonPath)).toString() const finish = '\n }\n}\n' - assert( - packageJson.endsWith(finish), - 'Template package.json ending unexpected.' - ) + assert(packageJson.endsWith(finish), 'Template package.json ending unexpected.') const replaced = packageJson.slice(0, -finish.length) + '\n },\n' + - ` "dependencies": {\n${convertListToJson( - versions, - deps[depKey], - ' ' - )}\n },\n` + - ` "devDependencies": {\n${convertListToJson( - versions, - deps[devDepKey], - ' ' - )}\n }\n` + + ` "dependencies": {\n${convertListToJson(versions, deps[depKey], ' ')}\n },\n` + + ` "devDependencies": {\n${convertListToJson(versions, deps[devDepKey], ' ')}\n }\n` + '}\n' await fs.promises.writeFile(packageJsonPath, replaced) } diff --git a/server/service/getServerPort.ts b/server/service/getServerPort.ts index 3a5988c6..fe871878 100644 --- a/server/service/getServerPort.ts +++ b/server/service/getServerPort.ts @@ -3,15 +3,13 @@ import { getPortPromise } from 'portfinder' let serverPort: Promise export const getServerPort = () => { - serverPort = - serverPort ?? - getPortPromise({ port: 10000 + Math.floor(Math.random() * 50000) }) + serverPort ??= getPortPromise({ port: 10000 + Math.floor(Math.random() * 50000) }) return serverPort } let clientPort: Promise export const getClientPort = () => { - clientPort = clientPort ?? getPortPromise({ port: 8000 }) + clientPort ??= getPortPromise({ port: 8000 }) return clientPort } diff --git a/server/service/localPath.ts b/server/service/localPath.ts index 256c200e..70a87d81 100644 --- a/server/service/localPath.ts +++ b/server/service/localPath.ts @@ -7,10 +7,7 @@ export type PathStatus = { } export const getPathStatus = async (path: string): Promise => { const [stat, isEmpty] = await Promise.all([ - fs.promises.stat(path).catch(() => ({ - isDirectory: () => false, - isFile: () => false - })), + fs.promises.stat(path).catch(() => ({ isDirectory: () => false, isFile: () => false })), fs.promises .readdir(path) .then((dir) => dir.length === 0) diff --git a/server/service/package-json.ts b/server/service/package-json.ts index 0e6203ca..bdff7c40 100644 --- a/server/service/package-json.ts +++ b/server/service/package-json.ts @@ -1,11 +1,6 @@ import assert from 'assert' -export const depKeys = [ - '@dep', - '@dev-dep', - '@server-dev-dep', - '@server-dep' -] as const +export const depKeys = ['@dep', '@dev-dep', '@server-dev-dep', '@server-dep'] as const export type DepKeys = (typeof depKeys)[number] export const isDepKey = (s: string): s is DepKeys => { @@ -25,6 +20,7 @@ export const getPackageVersions = (): { [packageName: string]: string } => { const strUniq = (list: string[]) => { const known = Object.create(null) list.forEach((el) => { + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion const g = el.match(/^(@?[^@]+)(?:@(.*))?$/)! known[g[1]] = g[2] ? `@${g[2]}` : '' }) @@ -55,9 +51,7 @@ export const convertListToJson = ( depVersion = g[2] } assert(depName in deps, `${depName} is not pre-defined.`) - return `${indent}${JSON.stringify(depName)}: ${JSON.stringify( - depVersion || deps[depName] - )}` + return `${indent}${JSON.stringify(depName)}: ${JSON.stringify(depVersion || deps[depName])}` }) .join(',\n') } diff --git a/server/tests/generate.spec.ts b/server/tests/generate.spec.ts index 172c1550..ee8c37c7 100644 --- a/server/tests/generate.spec.ts +++ b/server/tests/generate.spec.ts @@ -9,10 +9,7 @@ import tcpPortUsed from 'tcp-port-used' import path from 'path' import fs from 'fs' import { getPortPromise } from 'portfinder' -import { - cmdEscapeSingleInput, - shellEscapeSingleInput -} from '$/utils/shell/escape' +import { cmdEscapeSingleInput, shellEscapeSingleInput } from '$/utils/shell/escape' import fg from 'fast-glob' import YAML from 'yaml' import assert from 'assert' @@ -26,26 +23,20 @@ const execFileAsync = promisify(execFile) const randomNum = Number(process.env.TEST_CFA_RANDOM_NUM || '1') jest.setTimeout(1000 * 60 * 20) -// prettier-ignore const Red16x16PngBinary = Buffer.from([ 0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, 0x00, 0x00, 0x00, 0x0d, 0x49, 0x48, 0x44, 0x52, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x10, 0x08, 0x02, 0x00, 0x00, 0x00, 0x90, 0x91, 0x68, 0x36, 0x00, 0x00, 0x00, 0x17, 0x49, 0x44, 0x41, 0x54, 0x78, 0x9c, 0x63, 0xf8, 0xcf, 0xc0, 0x40, 0x12, 0x22, 0x4d, 0xf5, 0xa8, 0x86, 0x51, 0x0d, 0x43, 0x4a, 0x03, 0x00, 0x90, 0xf9, 0xff, 0x01, - 0xf9, 0xe1, 0xfa, 0x78, 0x00, 0x00, 0x00, 0x00, 0x49, 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, 0x82, + 0xf9, 0xe1, 0xfa, 0x78, 0x00, 0x00, 0x00, 0x00, 0x49, 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, 0x82 ]) const createShellRunner = (answers: Answers) => - `node ./bin/index --answers ${shellEscapeSingleInput( - JSON.stringify(answers) - )}` + `node ./bin/index --answers ${shellEscapeSingleInput(JSON.stringify(answers))}` const createCmdRunner = (answers: Answers) => `node ./bin/index --answers ${cmdEscapeSingleInput(JSON.stringify(answers))}` -const tempSandbox = async ( - answers: Answers, - main: (dir: string) => Promise -) => { +const tempSandbox = async (answers: Answers, main: (dir: string) => Promise) => { const tmpDir = process.env.TEST_CFA_TMP_DIR || '/tmp/cfa-test' try { await fs.promises.mkdir(tmpDir, { recursive: true }) @@ -65,32 +56,19 @@ const tempSandbox = async ( // NOTE: Sometimes failing on Windows } } catch (e: unknown) { - console.error( - `Failed. ${dir}\n${createCmdRunner(answers)}\n${createShellRunner( - answers - )}` - ) + console.error(`Failed. ${dir}\n${createCmdRunner(answers)}\n${createShellRunner(answers)}`) try { await fs.promises.writeFile( path.resolve(dir, '.test-error.txt'), e instanceof Error - ? e.name + - '\n\n' + - e.message + - '\n\nCall Stack\n' + - e.stack + - '\n\n' + - JSON.stringify(e) + ? e.name + '\n\n' + e.message + '\n\nCall Stack\n' + e.stack + '\n\n' + JSON.stringify(e) : String(e) ) } catch (e2: unknown) { // ignore } try { - await fs.promises.rename( - dir, - path.resolve(path.dirname(dir), path.basename(dir) + '-failed') - ) + await fs.promises.rename(dir, path.resolve(path.dirname(dir), path.basename(dir) + '-failed')) } catch (e2: unknown) { // ignore } @@ -106,14 +84,7 @@ test.each(Array.from({ length: randomNum }))('create', async () => { const clientPort = await getPortPromise({ port: randPort }) const serverPort = await getPortPromise({ port: clientPort + 1 }) await tempSandbox(answers, async (dir: string) => { - await generate( - { - ...answers, - clientPort, - serverPort - }, - path.resolve(__dirname, '..') - ) + await generate({ ...answers, clientPort, serverPort }, path.resolve(__dirname, '..')) expect((await fs.promises.stat(dir)).isDirectory()).toBe(true) await fs.promises.writeFile( path.resolve(dir, '.test-info.txt'), @@ -133,48 +104,27 @@ test.each(Array.from({ length: randomNum }))('create', async () => { expect(jsonFiles.length).toBeGreaterThan(0) for (const f of jsonFiles) { const content = (await fs.promises.readFile(f)).toString() - expect( - () => JSON.parse(content), - `JSON validation for ${f}` - ).not.toThrow() + expect(() => JSON.parse(content), `JSON validation for ${f}`).not.toThrow() } } // Validate all yaml files { - const yamlFiles = await fg([ - path.resolve(dir, '**/*.{yml,yaml}').replace(/\\/g, '/') - ]) + const yamlFiles = await fg([path.resolve(dir, '**/*.{yml,yaml}').replace(/\\/g, '/')]) for (const f of yamlFiles) { const content = (await fs.promises.readFile(f)).toString() - expect( - () => YAML.parse(content), - `YAML validation for ${f}` - ).not.toThrow() + expect(() => YAML.parse(content), `YAML validation for ${f}`).not.toThrow() } } - const templateCtx = answersToTemplateContext({ - ...answers, - serverPort: 0, - clientPort: 0 - }) - - const envFiles = await fg([ - path.resolve(dir, '**/.env').replace(/\\/g, '/') - ]) - const allEnv = envFiles - .map((f) => fs.readFileSync(f).toString()) - .join('\n') + const templateCtx = answersToTemplateContext({ ...answers, serverPort: 0, clientPort: 0 }) + const envFiles = await fg([path.resolve(dir, '**/.env').replace(/\\/g, '/')]) + const allEnv = envFiles.map((f) => fs.readFileSync(f).toString()).join('\n') assert(answers.pm) const npmClientPath = await realExecutablePath(answers.pm) // SQLite name found - if ( - answers.orm !== 'none' && - answers.orm !== 'typeorm' && - answers.db === 'sqlite' - ) { + if (answers.orm !== 'none' && answers.orm !== 'typeorm' && answers.db === 'sqlite') { expect(answers.sqliteDbFile?.length).toBeGreaterThan(0) expect(allEnv).toContain(answers.sqliteDbFile) } @@ -203,47 +153,33 @@ test.each(Array.from({ length: randomNum }))('create', async () => { await npmInstall(serverDir, npmClientPath, process.stdout) // eslint - await execFileAsync(npmClientPath, ['run', 'lint:fix'], { - cwd: dir - }) + await execFileAsync(npmClientPath, ['run', 'lint:fix'], { cwd: dir }) // typecheck - await execFileAsync(npmClientPath, ['run', 'typecheck'], { - cwd: dir - }) + await execFileAsync(npmClientPath, ['run', 'typecheck'], { cwd: dir }) // build:client - await execFileAsync(npmClientPath, ['run', 'build:client'], { - cwd: dir - }) + await execFileAsync(npmClientPath, ['run', 'build:client'], { cwd: dir }) // rename node_modules → node_modules_ignore await fs.promises.rename(nodeModulesDir, nodeModulesIgnoreDir) // build:server - await execFileAsync(npmClientPath, ['run', 'build:server'], { - cwd: dir - }) + await execFileAsync(npmClientPath, ['run', 'build:server'], { cwd: dir }) // rename node_modules_ignore → node_modules await fs.promises.rename(nodeModulesIgnoreDir, nodeModulesDir) // migrations if (answers.orm === 'prisma') { - await execFileAsync(npmClientPath, ['run', 'migrate:dev'], { - cwd: serverDir - }) + await execFileAsync(npmClientPath, ['run', 'migrate:dev'], { cwd: serverDir }) } else if (answers.orm === 'typeorm') { - await execFileAsync(npmClientPath, ['run', 'migration:run'], { - cwd: serverDir - }) + await execFileAsync(npmClientPath, ['run', 'migration:run'], { cwd: serverDir }) } // Project scope test if (answers.testing !== 'none') { - await execFileAsync(npmClientPath, ['test'], { - cwd: dir - }) + await execFileAsync(npmClientPath, ['test'], { cwd: dir }) } // Integration test @@ -255,19 +191,12 @@ test.each(Array.from({ length: randomNum }))('create', async () => { }) try { - await tcpPortUsed.waitUntilUsedOnHost( - serverPort, - '127.0.0.1', - 500, - 5000 - ) + await tcpPortUsed.waitUntilUsedOnHost(serverPort, '127.0.0.1', 500, 5000) const slash = answers.server === 'fastify' ? '' : '/' // Appearance test - const client = axios.create({ - baseURL: `http://localhost:${serverPort}` - }) + const client = axios.create({ baseURL: `http://localhost:${serverPort}` }) // There is no tasks at first { @@ -290,10 +219,7 @@ test.each(Array.from({ length: randomNum }))('create', async () => { client.get(`/api/user${slash}`, { headers: { authorization: 'token' } }) - ).rejects.toHaveProperty( - 'response.status', - answers.server === 'fastify' ? 400 : 401 - ) + ).rejects.toHaveProperty('response.status', answers.server === 'fastify' ? 400 : 401) // Cannot login with invalid password await expect( @@ -303,18 +229,13 @@ test.each(Array.from({ length: randomNum }))('create', async () => { // Create correct authorization using correct password const { data: { token } - } = await client.post(`/api/token${slash}`, { - id: 'id', - pass: 'pass' - }) + } = await client.post(`/api/token${slash}`, { id: 'id', pass: 'pass' }) const headers = { authorization: `Bearer ${token}` } // Get user information with credential { - const user = await client.get(`/api/user${slash}`, { - headers - }) + const user = await client.get(`/api/user${slash}`, { headers }) expect(user).toHaveProperty('data.name', 'sample user') expect(user).toHaveProperty( 'data.icon', @@ -323,10 +244,7 @@ test.each(Array.from({ length: randomNum }))('create', async () => { } const resStaticIcon = await client.get('/static/icons/dummy.svg') - expect(resStaticIcon.headers).toHaveProperty( - 'content-type', - 'image/svg+xml' - ) + expect(resStaticIcon.headers).toHaveProperty('content-type', 'image/svg+xml') const form = new FormData() // NOTE: Multer has a bug if there is no filename @@ -334,16 +252,11 @@ test.each(Array.from({ length: randomNum }))('create', async () => { form.append('icon', Red16x16PngBinary, 'red16x16.png') await client.post(`/api/user${slash}`, form, { - headers: { - ...headers, - ...form.getHeaders() - } + headers: { ...headers, ...form.getHeaders() } }) { - const user = await client.get(`/api/user${slash}`, { - headers - }) + const user = await client.get(`/api/user${slash}`, { headers }) expect(user).toHaveProperty( 'data.icon', expect.stringContaining('upload/icons/user-icon') @@ -355,12 +268,7 @@ test.each(Array.from({ length: randomNum }))('create', async () => { } finally { proc.kill() try { - await tcpPortUsed.waitUntilFreeOnHost( - serverPort, - '127.0.0.1', - 500, - 5000 - ) + await tcpPortUsed.waitUntilFreeOnHost(serverPort, '127.0.0.1', 500, 5000) } catch (e: unknown) { proc.kill('SIGKILL') } diff --git a/server/utils/answers/random.ts b/server/utils/answers/random.ts index 6e2cfd51..9a4cb789 100644 --- a/server/utils/answers/random.ts +++ b/server/utils/answers/random.ts @@ -3,10 +3,7 @@ import { Answers, cfaPrompts, isAnswersValid } from '$/common/prompts' import { AllDbContext } from '$/utils/database/context' import { randChoice } from '$/utils/random' -export const createRandomAnswers = async ( - dbCtx: AllDbContext, - num = 0 -): Promise => { +export const createRandomAnswers = async (dbCtx: AllDbContext, num = 0): Promise => { if (num === 1000) throw new Error('Infinite loop') const ans: Answers = {} @@ -14,9 +11,7 @@ export const createRandomAnswers = async ( cfaPrompts.forEach((p) => { ;(ans as any)[p.name] = p.default if (p.type === 'list') { - ;(ans as any)[p.name] = randChoice( - p.choices.map((choice) => choice.value) - ) + ;(ans as any)[p.name] = randChoice(p.choices.map((choice) => choice.value)) } else { // To pass the non-empty check ;(ans as any)[p.name] = 'test-foo' @@ -34,11 +29,8 @@ export const createRandomAnswers = async ( ]) // Database - if (ans.orm === 'typeorm' && ans.db === 'sqlite') { - ans.db = 'mysql' - } - if (process.env.TEST_CFA_FIX_CLIENT) - ans.client = process.env.TEST_CFA_FIX_CLIENT + if (ans.orm === 'typeorm' && ans.db === 'sqlite') ans.db = 'mysql' + if (process.env.TEST_CFA_FIX_CLIENT) ans.client = process.env.TEST_CFA_FIX_CLIENT if (process.env.TEST_CFA_FIX_DB) ans.db = process.env.TEST_CFA_FIX_DB if (process.env.TEST_CFA_FIX_ORM) ans.orm = process.env.TEST_CFA_FIX_ORM if (ans.db === 'sqlite') { diff --git a/server/utils/database/jest-context.ts b/server/utils/database/jest-context.ts index 174bc926..bf8bfd8e 100644 --- a/server/utils/database/jest-context.ts +++ b/server/utils/database/jest-context.ts @@ -1,9 +1,4 @@ -import { - AllDbContext, - createMysqlContext, - createPgContext, - createSqliteContext -} from './context' +import { AllDbContext, createMysqlContext, createPgContext, createSqliteContext } from './context' import path from 'path' export const createJestDbContext = (): AllDbContext => { diff --git a/server/utils/shell/escape.ts b/server/utils/shell/escape.ts index 55a4494b..6b228f82 100644 --- a/server/utils/shell/escape.ts +++ b/server/utils/shell/escape.ts @@ -6,10 +6,7 @@ export const shellEscapeSingleInput = (s: string) => { // Reference: https://thinca.hatenablog.com/entry/20100210/1265813598 export const cmdEscapeSingleInput = (s: string) => { const escSpecial = s.replace(/[&|<>()^%]/g, (c) => c + '^') - const escBackslash = escSpecial.replace( - /\\+"/g, - (c) => c.slice(0, -1).repeat(2) + '"' - ) + const escBackslash = escSpecial.replace(/\\+"/g, (c) => c.slice(0, -1).repeat(2) + '"') const escDoubleQuotes = escBackslash.replace(/"/g, '\\^"') return '^"' + escDoubleQuotes + '^"' } diff --git a/utils/apiClient.ts b/utils/apiClient.ts index c08aeb8e..c1cb2ef6 100644 --- a/utils/apiClient.ts +++ b/utils/apiClient.ts @@ -1,9 +1,4 @@ import aspida from '@aspida/axios' import api from '~/server/api/$api' -export const createApiClient = () => - api( - aspida(undefined, { - baseURL: '/api' - }) - ) +export const createApiClient = () => api(aspida(undefined, { baseURL: '/api' }))