diff --git a/packages/cli/package.json b/packages/cli/package.json index 5b8757d9ad..4517a3ccca 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -92,6 +92,7 @@ "@babel/cli": "7.28.3", "@babel/core": "^7.26.10", "@types/archiver": "^6", + "@types/pluralize": "0.0.33", "memfs": "4.51.1", "node-ssh": "13.2.1", "tsx": "4.21.0", diff --git a/packages/cli/src/commands/destroy/scaffold/scaffoldHandler.js b/packages/cli/src/commands/destroy/scaffold/scaffoldHandler.js index bc908b4e35..2c6b550ffa 100644 --- a/packages/cli/src/commands/destroy/scaffold/scaffoldHandler.js +++ b/packages/cli/src/commands/destroy/scaffold/scaffoldHandler.js @@ -3,6 +3,7 @@ import pascalcase from 'pascalcase' import { recordTelemetryAttributes } from '@cedarjs/cli-helpers' +import { pluralize } from '../../../lib/cedarPluralize.js' import c from '../../../lib/colors.js' import { deleteFilesTask, @@ -11,7 +12,6 @@ import { removeRoutesFromRouterTask, writeFile, } from '../../../lib/index.js' -import { pluralize } from '../../../lib/rwPluralize.js' import { verifyModelName } from '../../../lib/schemaHelpers.js' import { files, diff --git a/packages/cli/src/commands/generate/cell/cellHandler.js b/packages/cli/src/commands/generate/cell/cellHandler.js index a4bf4b1dbe..33084a9bc4 100644 --- a/packages/cli/src/commands/generate/cell/cellHandler.js +++ b/packages/cli/src/commands/generate/cell/cellHandler.js @@ -2,10 +2,10 @@ import pascalcase from 'pascalcase' import { generate as generateTypes } from '@cedarjs/internal/dist/generate/generate' +import { isPlural, singularize } from '../../../lib/cedarPluralize.js' import { nameVariants, transformTSToJS } from '../../../lib/index.js' import { isWordPluralizable } from '../../../lib/pluralHelpers.js' import { addFunctionToRollback } from '../../../lib/rollback.js' -import { isPlural, singularize } from '../../../lib/rwPluralize.js' import { getSchema } from '../../../lib/schemaHelpers.js' import { forcePluralizeWord, removeGeneratorName } from '../helpers.js' import { diff --git a/packages/cli/src/commands/generate/helpers.js b/packages/cli/src/commands/generate/helpers.ts similarity index 73% rename from packages/cli/src/commands/generate/helpers.js rename to packages/cli/src/commands/generate/helpers.ts index 10bbc2e62d..9d6b76808c 100644 --- a/packages/cli/src/commands/generate/helpers.js +++ b/packages/cli/src/commands/generate/helpers.ts @@ -6,17 +6,18 @@ // CWD, plus importing this file statically also makes the CLI startup time // much slower +import type { DMMF } from '@prisma/generator-helper' import { paramCase } from 'change-case' import pascalcase from 'pascalcase' -import { pluralize, isPlural, isSingular } from '../../lib/rwPluralize.js' +import { pluralize, isPlural, isSingular } from '../../lib/cedarPluralize.js' /** * Creates a route path, either returning the existing path if passed, or * creating one based on the name. If the passed path is just a route parameter * a new path based on the name is created, with the parameter appended to it */ -export const pathName = (path, name) => { +export const pathName = (path: string | undefined, name: string) => { let routePath = path if (path && path.startsWith('{') && path.endsWith('}')) { @@ -30,8 +31,7 @@ export const pathName = (path, name) => { return routePath } -/** @type {(name: string, generatorName: string) => string } **/ -export function removeGeneratorName(name, generatorName) { +export function removeGeneratorName(name: string, generatorName: string) { // page -> Page const pascalComponentName = pascalcase(generatorName) @@ -41,7 +41,7 @@ export function removeGeneratorName(name, generatorName) { return coercedName } -export const validateName = (name) => { +export const validateName = (name: string) => { if (name.match(/^\W/)) { throw new Error( 'The argument must start with a letter, number or underscore.', @@ -50,7 +50,7 @@ export const validateName = (name) => { } // Returns all relations to other models -export const relationsForModel = (model) => { +export const relationsForModel = (model: DMMF.Model) => { return model.fields .filter((f) => f.relationName) .map((field) => { @@ -59,7 +59,7 @@ export const relationsForModel = (model) => { } // Returns only relations that are of datatype Int -export const intForeignKeysForModel = (model) => { +export const intForeignKeysForModel = (model: DMMF.Model) => { return model.fields .filter((f) => f.name.match(/Id$/) && f.type === 'Int') .map((f) => f.name) @@ -68,7 +68,7 @@ export const intForeignKeysForModel = (model) => { /** * Adds "List" to the end of words we can't pluralize */ -export const forcePluralizeWord = (word) => { +export const forcePluralizeWord = (word: string) => { // If word is both plural and singular (like equipment), then append "List" if (isPlural(word) && isSingular(word)) { return pascalcase(`${word}_list`) @@ -77,20 +77,21 @@ export const forcePluralizeWord = (word) => { return pluralize(word) } -/** @type {(paramType: 'Int' | 'Float' | 'Boolean' | 'String') => string } **/ -export const mapRouteParamTypeToTsType = (paramType) => { - const routeParamToTsType = { +export const mapRouteParamTypeToTsType = ( + paramType: 'Int' | 'Float' | 'Boolean' | 'String', +) => { + const routeParamToTsType: Record = { Int: 'number', Float: 'number', Boolean: 'boolean', String: 'string', } + return routeParamToTsType[paramType] || 'unknown' } -/** @type {(scalarType: 'String' | 'Boolean' | 'Int' | 'BigInt' | 'Float' | 'Decimal' | 'DateTime' | 'Bytes' ) => string } **/ -export const mapPrismaScalarToPagePropTsType = (scalarType) => { - const prismaScalarToTsType = { +export const mapPrismaScalarToPagePropTsType = (scalarType: string) => { + const prismaScalarToTsType: Record = { String: 'string', Boolean: 'boolean', Int: 'number', @@ -100,5 +101,6 @@ export const mapPrismaScalarToPagePropTsType = (scalarType) => { DateTime: 'string', Bytes: 'Uint8Array', } + return prismaScalarToTsType[scalarType] || 'unknown' } diff --git a/packages/cli/src/commands/generate/scaffold/scaffoldHandler.js b/packages/cli/src/commands/generate/scaffold/scaffoldHandler.js index a85d39a6be..828371d60c 100644 --- a/packages/cli/src/commands/generate/scaffold/scaffoldHandler.js +++ b/packages/cli/src/commands/generate/scaffold/scaffoldHandler.js @@ -12,6 +12,7 @@ import { recordTelemetryAttributes } from '@cedarjs/cli-helpers' import { generate as generateTypes } from '@cedarjs/internal/dist/generate/generate' import { getConfig } from '@cedarjs/project-config' +import { pluralize, singularize } from '../../../lib/cedarPluralize.js' import c from '../../../lib/colors.js' import { generateTemplate, @@ -29,7 +30,6 @@ import { prepareForRollback, addFunctionToRollback, } from '../../../lib/rollback.js' -import { pluralize, singularize } from '../../../lib/rwPluralize.js' import { getSchema, verifyModelName } from '../../../lib/schemaHelpers.js' import { relationsForModel, diff --git a/packages/cli/src/commands/generate/sdl/sdlHandler.js b/packages/cli/src/commands/generate/sdl/sdlHandler.js index 839766cd1e..41b2808f46 100644 --- a/packages/cli/src/commands/generate/sdl/sdlHandler.js +++ b/packages/cli/src/commands/generate/sdl/sdlHandler.js @@ -10,6 +10,7 @@ import { generate as generateTypes } from '@cedarjs/internal/dist/generate/gener import { getConfig } from '@cedarjs/project-config' import { errorTelemetry } from '@cedarjs/telemetry' +import { pluralize } from '../../../lib/cedarPluralize.js' import c from '../../../lib/colors.js' import { generateTemplate, @@ -21,7 +22,6 @@ import { prepareForRollback, addFunctionToRollback, } from '../../../lib/rollback.js' -import { pluralize } from '../../../lib/rwPluralize.js' import { getSchema, getEnum, diff --git a/packages/cli/src/commands/generate/service/serviceHandler.js b/packages/cli/src/commands/generate/service/serviceHandler.js index 81e1e795bd..e9d8184d07 100644 --- a/packages/cli/src/commands/generate/service/serviceHandler.js +++ b/packages/cli/src/commands/generate/service/serviceHandler.js @@ -1,7 +1,7 @@ import camelcase from 'camelcase' +import { pluralize, singularize } from '../../../lib/cedarPluralize.js' import { transformTSToJS } from '../../../lib/index.js' -import { pluralize, singularize } from '../../../lib/rwPluralize.js' import { getSchema, verifyModelName } from '../../../lib/schemaHelpers.js' import { relationsForModel } from '../helpers.js' import { diff --git a/packages/cli/src/commands/generate/yargsCommandHelpers.js b/packages/cli/src/commands/generate/yargsCommandHelpers.ts similarity index 76% rename from packages/cli/src/commands/generate/yargsCommandHelpers.js rename to packages/cli/src/commands/generate/yargsCommandHelpers.ts index 051479a543..4915e07695 100644 --- a/packages/cli/src/commands/generate/yargsCommandHelpers.js +++ b/packages/cli/src/commands/generate/yargsCommandHelpers.ts @@ -1,5 +1,6 @@ // This file is safe to statically import in the CLI import { terminalLink } from 'termi-link' +import type { Argv, Options, PositionalOptions } from 'yargs' // Don't import anything here that isn't already imported by the CLI import { isTypeScriptProject } from '@cedarjs/cli-helpers' @@ -10,9 +11,8 @@ import { isTypeScriptProject } from '@cedarjs/cli-helpers' * The reason for this is that this in turn will call `isTypeScriptProject`, * and that has side effects that will break `cwd` functionality if called * before `cwd` is initialized. - * @type {() => Record} */ -export const getYargsDefaults = () => ({ +export const getYargsDefaults = (): Record => ({ force: { alias: 'f', default: false, @@ -27,7 +27,10 @@ export const getYargsDefaults = () => ({ }, }) -const appendPositionalsToCmd = (commandString, positionalsObj) => { +const appendPositionalsToCmd = ( + commandString: string, + positionalsObj: Record, +) => { // Add positionals like `page ` + ` [path]` if specified if (Object.keys(positionalsObj).length > 0) { const positionalNames = Object.keys(positionalsObj) @@ -40,16 +43,29 @@ const appendPositionalsToCmd = (commandString, positionalsObj) => { } } -export function createCommand(componentName, positionalsObj = {}) { +export function createCommand( + componentName: string, + positionalsObj: Record = {}, +) { return appendPositionalsToCmd(`${componentName} `, positionalsObj) } -export function createDescription(componentName) { +export function createDescription(componentName: string) { return `Generate a ${componentName} component` } -export function createBuilder({ componentName, optionsObj, positionalsObj }) { - return (yargs) => { +interface CreateBuilderOptions { + componentName: string + optionsObj?: Record | (() => Record) + positionalsObj?: Record +} + +export function createBuilder({ + componentName, + optionsObj, + positionalsObj, +}: CreateBuilderOptions) { + return (yargs: Argv) => { yargs .positional('name', { description: `Name of the ${componentName}`, @@ -99,8 +115,8 @@ export function createBuilder({ componentName, optionsObj, positionalsObj }) { } } -export function createHandler(componentName) { - return async function handler(argv) { +export function createHandler(componentName: string) { + return async function handler(argv: any) { const { handler: importedHandler } = await import( `./${componentName}/${componentName}Handler.js` ) diff --git a/packages/cli/src/lib/__tests__/rwPluralize.test.js b/packages/cli/src/lib/__tests__/cedarPluralize.test.js similarity index 99% rename from packages/cli/src/lib/__tests__/rwPluralize.test.js rename to packages/cli/src/lib/__tests__/cedarPluralize.test.js index dda77f32f4..50728bbb37 100644 --- a/packages/cli/src/lib/__tests__/rwPluralize.test.js +++ b/packages/cli/src/lib/__tests__/cedarPluralize.test.js @@ -6,7 +6,7 @@ import { isPlural, isSingular, addSingularPlural, -} from '../rwPluralize.js' +} from '../cedarPluralize.js' test('pluralize', () => { expect(pluralize('books')).toEqual('books') diff --git a/packages/cli/src/lib/__tests__/pluralHelpers.test.js b/packages/cli/src/lib/__tests__/pluralHelpers.test.js index 2cc52b7f24..e6c8835940 100644 --- a/packages/cli/src/lib/__tests__/pluralHelpers.test.js +++ b/packages/cli/src/lib/__tests__/pluralHelpers.test.js @@ -1,8 +1,8 @@ import prompts from 'prompts' import { test, expect } from 'vitest' +import { pluralize, singularize } from '../cedarPluralize.js' import * as helpers from '../pluralHelpers.js' -import { pluralize, singularize } from '../rwPluralize.js' test('validatePlural returns true if plural is single word and unique from singular', () => { const result = helpers.validatePlural('plural', 'singular') diff --git a/packages/cli/src/lib/rwPluralize.js b/packages/cli/src/lib/cedarPluralize.ts similarity index 77% rename from packages/cli/src/lib/rwPluralize.js rename to packages/cli/src/lib/cedarPluralize.ts index 4196883a45..cc928b251f 100644 --- a/packages/cli/src/lib/rwPluralize.js +++ b/packages/cli/src/lib/cedarPluralize.ts @@ -1,28 +1,27 @@ import plurals from 'pluralize' -const mappings = { +const mappings: { + toSingular: Record + toPlural: Record +} = { toSingular: {}, toPlural: {}, } /** * Find Bar in FooBazBar - * - * @type {(str: string) => string } */ -function lastWord(str) { +function lastWord(str: string) { const capitals = str.match(/[A-Z]/g) - const lastIndex = str.lastIndexOf(capitals?.slice(-1)[0]) + const lastIndex = str.lastIndexOf(capitals?.slice(-1)[0] ?? '') return lastIndex >= 0 ? str.slice(lastIndex) : str } /** * Returns the plural form of the given word - * - * @type {(word: string) => string } */ -export function pluralize(word) { +export function pluralize(word: string) { if (mappings.toPlural[word]) { return mappings.toPlural[word] } @@ -42,10 +41,8 @@ export function pluralize(word) { /** * Returns the singular form of the given word - * - * @type {(word: string) => string } */ -export function singularize(word) { +export function singularize(word: string) { if (mappings.toSingular[word]) { return mappings.toSingular[word] } @@ -60,13 +57,17 @@ export function singularize(word) { return base + plurals.singular(plural) } -/** @type {(word: string) => boolean } */ -export function isPlural(word) { +/** + * Returns true if the given word is plural + */ +export function isPlural(word: string) { return plurals.isPlural(lastWord(word)) } -/** @type {(word: string) => boolean } */ -export function isSingular(word) { +/** + * Returns true if the given word is singular + */ +export function isSingular(word: string) { return plurals.isSingular(lastWord(word)) } @@ -95,18 +96,16 @@ export function isSingular(word) { * Furthermore we need to handle changing the mappings (this is only done in * tests). So if the method is called again, with input 'pokemon', 'pokemons' * all the old mappings first have to be removed, before adding the new ones - * - * @param {string} singular - * @param {string} plural - * - * @returns {undefined} */ -export function addSingularPlural(singular, plural) { +export function addSingularPlural(singular: string, plural: string) { const existingPlural = Object.keys(mappings.toSingular).find( (key) => mappings.toSingular[key] === singular, ) - delete mappings.toSingular[existingPlural] - delete mappings.toPlural[existingPlural] + + if (existingPlural) { + delete mappings.toSingular[existingPlural] + delete mappings.toPlural[existingPlural] + } mappings.toPlural[singular] = plural mappings.toPlural[plural] = plural diff --git a/packages/cli/src/lib/index.js b/packages/cli/src/lib/index.js index 96b0124ca7..1989482d53 100644 --- a/packages/cli/src/lib/index.js +++ b/packages/cli/src/lib/index.js @@ -20,9 +20,9 @@ import { findUp, } from '@cedarjs/project-config' +import { pluralize, singularize } from './cedarPluralize.js' import c from './colors.js' import { addFileToRollback } from './rollback.js' -import { pluralize, singularize } from './rwPluralize.js' export { findUp } diff --git a/packages/cli/src/lib/pluralHelpers.js b/packages/cli/src/lib/pluralHelpers.ts similarity index 75% rename from packages/cli/src/lib/pluralHelpers.js rename to packages/cli/src/lib/pluralHelpers.ts index 77f4a17460..d72314105a 100644 --- a/packages/cli/src/lib/pluralHelpers.js +++ b/packages/cli/src/lib/pluralHelpers.ts @@ -1,35 +1,44 @@ import prompts from 'prompts' -import { isSingular, isPlural, addSingularPlural } from './rwPluralize.js' +import { isSingular, isPlural, addSingularPlural } from './cedarPluralize.js' -export const isWordPluralizable = (word) => { +export const isWordPluralizable = (word: string) => { return isPlural(word) !== isSingular(word) } -export const validatePlural = (plural, singular) => { +export const validatePlural = (plural: string, singular: string) => { const trimmedPlural = plural.trim() if (trimmedPlural === singular) { return 'Plural can not be same as singular.' } + if (trimmedPlural.match(/[\n\r\s]+/)) { return 'Only one word please!' } + // Control Char u0017 is returned if default input is cleared in the prompt // using option+backspace // eslint-disable-next-line no-control-regex if (trimmedPlural.match(/^[\n\r\s\u0017]*$/)) { return 'Plural can not be empty.' } + return true } +interface EnsureUniquePluralOptions { + model: string + isDestroyer?: boolean + forcePrompt?: boolean +} + // Ask user for plural version, if singular & plural are same for a word. For // example: Pokemon export const ensureUniquePlural = async ({ model, isDestroyer = false, forcePrompt = false, -}) => { +}: EnsureUniquePluralOptions) => { if (!forcePrompt && isWordPluralizable(model)) { return } @@ -45,14 +54,14 @@ export const ensureUniquePlural = async ({ const promptMessage = isDestroyer ? destroyMessage : generateMessage // News => Newses; Equipment => Equipments - const initialPlural = model.slice(-1) === 's' ? `${model}es` : `${model}s` + const initialPlural = model.at(-1) === 's' ? `${model}es` : `${model}s` - const promptResult = await prompts({ + const promptResult: { plural: string | undefined } = await prompts({ type: 'text', name: 'plural', message: promptMessage, initial: initialPlural, - validate: (pluralInput) => validatePlural(pluralInput, model), + validate: (pluralInput: string) => validatePlural(pluralInput, model), }) // Quick-fix is to remove that control char u0017, which is prepended if diff --git a/packages/cli/src/lib/schemaHelpers.js b/packages/cli/src/lib/schemaHelpers.js index 1a534c0e55..6a752a0690 100644 --- a/packages/cli/src/lib/schemaHelpers.js +++ b/packages/cli/src/lib/schemaHelpers.js @@ -2,8 +2,8 @@ import prismaInternals from '@prisma/internals' import { getSchemaPath } from '@cedarjs/project-config' +import { singularize, isPlural } from './cedarPluralize.js' import { ensureUniquePlural } from './pluralHelpers.js' -import { singularize, isPlural } from './rwPluralize.js' import { getPaths } from './index.js' diff --git a/yarn.lock b/yarn.lock index 93784f261e..5ba46e97cd 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3375,6 +3375,7 @@ __metadata: "@opentelemetry/semantic-conventions": "npm:1.22.0" "@prisma/internals": "npm:6.19.1" "@types/archiver": "npm:^6" + "@types/pluralize": "npm:0.0.33" ansis: "npm:4.1.0" archiver: "npm:7.0.1" boxen: "npm:5.1.2" @@ -12176,6 +12177,13 @@ __metadata: languageName: node linkType: hard +"@types/pluralize@npm:0.0.33": + version: 0.0.33 + resolution: "@types/pluralize@npm:0.0.33" + checksum: 10c0/24899caf85b79dd291a6b6e9b9f3b67b452b18d578d0ac0d531a705bf5ee0361d9386ea1f8532c64de9e22c6e9606c5497787bb5e31bd299c487980436c59785 + languageName: node + linkType: hard + "@types/prettier@npm:^2.0.0": version: 2.7.3 resolution: "@types/prettier@npm:2.7.3"