diff --git a/.changeset/pre.json b/.changeset/pre.json new file mode 100644 index 000000000..87c7ea9c1 --- /dev/null +++ b/.changeset/pre.json @@ -0,0 +1,16 @@ +{ + "mode": "pre", + "tag": "next", + "initialVersions": { + "@kitql-old/all-in": "0.9.6", + "create-kitql": "0.0.3", + "eslint-config-kitql": "0.0.2", + "@kitql/handles": "0.1.3", + "@kitql/helpers": "0.8.4", + "vite-plugin-kit-routes": "0.1.4", + "vite-plugin-striper": "0.0.6", + "vite-plugin-watch-and-run": "1.4.4", + "website": "1.1.2" + }, + "changesets": [] +} diff --git a/.changeset/red-mice-brush.md b/.changeset/red-mice-brush.md new file mode 100644 index 000000000..d5687aff1 --- /dev/null +++ b/.changeset/red-mice-brush.md @@ -0,0 +1,5 @@ +--- +'vite-plugin-kit-routes': minor +--- + +BREAKING: remove optional params in the key diff --git a/.changeset/strong-snails-wink.md b/.changeset/strong-snails-wink.md new file mode 100644 index 000000000..514f24b55 --- /dev/null +++ b/.changeset/strong-snails-wink.md @@ -0,0 +1,5 @@ +--- +'vite-plugin-kit-routes': minor +--- + +action "default" needs to be specified, we want to be explicite (will help the route() function & avoid collision) diff --git a/packages/vite-plugin-kit-routes/package.json b/packages/vite-plugin-kit-routes/package.json index eeb78def4..c9bcd27b9 100644 --- a/packages/vite-plugin-kit-routes/package.json +++ b/packages/vite-plugin-kit-routes/package.json @@ -16,7 +16,7 @@ "scripts": { "prepare": "svelte-kit sync", "dev": "vite dev", - "build": "vite build && svelte-package && node ../../scripts/package.js", + "build": "vite build && svelte-package && pnpm check && node ../../scripts/package.js", "preview": "vite preview", "package": "svelte-package && publint", "check": "svelte-check --tsconfig ./tsconfig.json", diff --git a/packages/vite-plugin-kit-routes/src/lib/ROUTES.ts b/packages/vite-plugin-kit-routes/src/lib/ROUTES.ts index 1128d1d6f..04e808b32 100644 --- a/packages/vite-plugin-kit-routes/src/lib/ROUTES.ts +++ b/packages/vite-plugin-kit-routes/src/lib/ROUTES.ts @@ -4,64 +4,65 @@ * >> DO NOT EDIT THIS FILE MANUALLY << */ +/** + * PAGES + */ export const PAGES = { _ROOT: `/`, subGroup: `/subGroup`, subGroup2: (params: { first: string | number }) => { - return `/subGroup2${appendSp({ first: params.first })}` + return `/subGroup2${appendSp({ first: params?.first })}` }, - lang_contract: (params: { lang?: 'fr' | 'en' | 'hu' | 'at' | string } = {}) => { + contract: (params?: { lang?: 'fr' | 'en' | 'hu' | 'at' | string }) => { return `${params?.lang ? `/${params?.lang}` : ''}/contract` }, - lang_contract_id: (params: { - lang?: 'fr' | 'en' | 'hu' | 'at' | string - id: string | number - }) => { + contract_id: (params: { id: string | number; lang?: 'fr' | 'en' | 'hu' | 'at' | string }) => { return `${params?.lang ? `/${params?.lang}` : ''}/contract/${params.id}` }, - lang_gp_one: (params: { lang?: 'fr' | 'en' | 'hu' | 'at' | string } = {}) => { + gp_one: (params?: { lang?: 'fr' | 'en' | 'hu' | 'at' | string }) => { return `${params?.lang ? `/${params?.lang}` : ''}/gp/one` }, - lang_gp_two: (params: { lang?: 'fr' | 'en' | 'hu' | 'at' | string } = {}) => { + gp_two: (params?: { lang?: 'fr' | 'en' | 'hu' | 'at' | string }) => { return `${params?.lang ? `/${params?.lang}` : ''}/gp/two` }, - lang_main: (params: { lang?: 'fr' | 'en' | 'hu' | 'at' | string } = {}) => { + main: (params?: { lang?: 'fr' | 'en' | 'hu' | 'at' | string }) => { return `${params?.lang ? `/${params?.lang}` : ''}/main` }, - lang_match_id_int: (params: { - lang?: 'fr' | 'en' | 'hu' | 'at' | string - id: string | number - }) => { + match_id_int: (params: { id: string | number; lang?: 'fr' | 'en' | 'hu' | 'at' | string }) => { return `${params?.lang ? `/${params?.lang}` : ''}/match/${params.id}` }, - lang_site: ( - params: { lang?: 'fr' | 'en' | 'hu' | 'at' | string; limit?: number } = {}, + site: ( + params?: { lang?: 'fr' | 'en' | 'hu' | 'at' | string; limit?: number }, sp?: Record, ) => { return `${params?.lang ? `/${params?.lang}` : ''}/site${appendSp({ + limit: params?.limit, ...sp, - limit: params.limit, })}` }, - lang_site_id: ( - params: { lang?: 'fr' | 'hu' | undefined; id?: string; limit?: number; demo?: string } = {}, - ) => { + site_id: (params?: { + lang?: 'fr' | 'hu' | undefined + id?: string + limit?: number + demo?: string + }) => { + params = params ?? {} params.lang = params.lang ?? 'fr' params.id = params.id ?? 'Vienna' return `${params?.lang ? `/${params?.lang}` : ''}/site/${params.id}${appendSp({ - limit: params.limit, - demo: params.demo, + limit: params?.limit, + demo: params?.demo, })}` }, - lang_site_contract_siteId_contractId: (params: { - lang?: 'fr' | 'en' | 'hu' | 'at' | string + site_contract_siteId_contractId: (params: { siteId: string | number contractId: string | number + lang?: 'fr' | 'en' | 'hu' | 'at' | string limit?: number }) => { return `${params?.lang ? `/${params?.lang}` : ''}/site_contract/${params.siteId}-${ params.contractId - }${appendSp({ limit: params.limit })}` + }${appendSp({ limit: params?.limit })}` }, a_rest_z: (params: { rest: (string | number)[] }) => { return `/a/${params.rest?.join('/')}/z` @@ -71,59 +72,70 @@ export const PAGES = { lay_skip: `/lay/skip`, } +/** + * SERVERS + */ export const SERVERS = { - lang_contract: ( - method: 'GET' | 'POST', - params: { lang?: 'fr' | 'en' | 'hu' | 'at' | string } = {}, - ) => { + GET_contract: (params?: { lang?: 'fr' | 'en' | 'hu' | 'at' | string }) => { return `${params?.lang ? `/${params?.lang}` : ''}/contract` }, - lang_site: (method: 'GET', params: { lang?: 'fr' | 'en' | 'hu' | 'at' | string } = {}) => { - return `${params?.lang ? `/${params?.lang}` : ''}/site` + POST_contract: (params?: { lang?: 'fr' | 'en' | 'hu' | 'at' | string }) => { + return `${params?.lang ? `/${params?.lang}` : ''}/contract` }, - api_graphql: (method: 'GET' | 'POST') => { - return `/api/graphql` + GET_site: (params?: { lang?: 'fr' | 'en' | 'hu' | 'at' | string }) => { + return `${params?.lang ? `/${params?.lang}` : ''}/site` }, + GET_api_graphql: `/api/graphql`, + POST_api_graphql: `/api/graphql`, } +/** + * ACTIONS + */ export const ACTIONS = { - lang_contract_id: (params: { - lang?: 'fr' | 'en' | 'hu' | 'at' | string + default_contract_id: (params: { id: string | number + lang?: 'fr' | 'en' | 'hu' | 'at' | string limit?: number }) => { return `${params?.lang ? `/${params?.lang}` : ''}/contract/${params.id}${appendSp({ - limit: params.limit, + limit: params?.limit, })}` }, - lang_site: ( - action: 'action1' | 'action2', - params: { lang?: 'fr' | 'en' | 'hu' | 'at' | string } = {}, - ) => { - return `${params?.lang ? `/${params?.lang}` : ''}/site?/${action}` + create_site: (params?: { + lang?: 'fr' | 'en' | 'hu' | 'at' | string + redirectTo?: 'list' | 'new' | 'detail' + }) => { + return `${params?.lang ? `/${params?.lang}` : ''}/site?/create${appendSp( + { redirectTo: params?.redirectTo }, + '&', + )}` }, - lang_site_contract: ( - action: 'noSatisfies', - params: { lang?: 'fr' | 'en' | 'hu' | 'at' | string } = {}, - ) => { - return `${params?.lang ? `/${params?.lang}` : ''}/site_contract?/${action}` - }, - lang_site_contract_siteId_contractId: ( - action: 'sendSomething', - params: { - lang?: 'fr' | 'en' | 'hu' | 'at' | string - siteId: string | number - contractId: string | number - extra?: 'A' | 'B' - }, - ) => { + update_site_id: (params: { id: string | number; lang?: 'fr' | 'en' | 'hu' | 'at' | string }) => { + return `${params?.lang ? `/${params?.lang}` : ''}/site/${params.id}?/update` + }, + delete_site_id: (params: { id: string | number; lang?: 'fr' | 'en' | 'hu' | 'at' | string }) => { + return `${params?.lang ? `/${params?.lang}` : ''}/site/${params.id}?/delete` + }, + noSatisfies_site_contract: (params?: { lang?: 'fr' | 'en' | 'hu' | 'at' | string }) => { + return `${params?.lang ? `/${params?.lang}` : ''}/site_contract?/noSatisfies` + }, + send_site_contract_siteId_contractId: (params: { + siteId: string | number + contractId: string | number + lang?: 'fr' | 'en' | 'hu' | 'at' | string + extra?: 'A' | 'B' + }) => { params.extra = params.extra ?? 'A' return `${params?.lang ? `/${params?.lang}` : ''}/site_contract/${params.siteId}-${ params.contractId - }?/${action}${appendSp({ extra: params.extra }, '&')}` + }?/send${appendSp({ extra: params?.extra }, '&')}` }, } +/** + * LINKS + */ export const LINKS = { twitter: `https:/twitter.com/jycouet`, twitter_post: (params: { name: string | number; id: string | number }) => { @@ -132,10 +144,13 @@ export const LINKS = { gravatar: (params: { str: string; s?: number; d?: 'retro' | 'identicon' }) => { params.s = params.s ?? 75 params.d = params.d ?? 'identicon' - return `https:/www.gravatar.com/avatar/${params.str}${appendSp({ s: params.s, d: params.d })}` + return `https:/www.gravatar.com/avatar/${params.str}${appendSp({ s: params?.s, d: params?.d })}` }, } +/** + * Append search params to a string + */ const appendSp = (sp?: Record, prefix: '?' | '&' = '?') => { if (sp === undefined) return '' const mapping = Object.entries(sp) @@ -159,7 +174,7 @@ const appendSp = (sp?: Record, prefix: '?' * * kitRoutes({ * PAGES: { - * // here, "paths" it will be typed! + * // here, key of object will be typed! * } * }) * ``` @@ -169,26 +184,34 @@ export type KIT_ROUTES = { _ROOT: never subGroup: never subGroup2: never - lang_contract: 'lang' - lang_contract_id: 'lang' | 'id' - lang_gp_one: 'lang' - lang_gp_two: 'lang' - lang_main: 'lang' - lang_match_id_int: 'lang' | 'id' - lang_site: 'lang' - lang_site_id: 'lang' | 'id' - lang_site_contract_siteId_contractId: 'lang' | 'siteId' | 'contractId' + contract: 'lang' + contract_id: 'id' | 'lang' + gp_one: 'lang' + gp_two: 'lang' + main: 'lang' + match_id_int: 'id' | 'lang' + site: 'lang' + site_id: 'lang' | 'id' + site_contract_siteId_contractId: 'siteId' | 'contractId' | 'lang' a_rest_z: 'rest' lay_normal: never lay_root_layout: never lay_skip: never } - SERVERS: { lang_contract: 'lang'; lang_site: 'lang'; api_graphql: never } + SERVERS: { + GET_contract: 'lang' + POST_contract: 'lang' + GET_site: 'lang' + GET_api_graphql: never + POST_api_graphql: never + } ACTIONS: { - lang_contract_id: 'lang' | 'id' - lang_site: 'lang' - lang_site_contract: 'lang' - lang_site_contract_siteId_contractId: 'lang' | 'siteId' | 'contractId' + default_contract_id: 'id' | 'lang' + create_site: 'lang' + update_site_id: 'id' | 'lang' + delete_site_id: 'id' | 'lang' + noSatisfies_site_contract: 'lang' + send_site_contract_siteId_contractId: 'siteId' | 'contractId' | 'lang' } LINKS: { twitter: never; twitter_post: 'name' | 'id'; gravatar: 'str' } Params: { @@ -200,6 +223,7 @@ export type KIT_ROUTES = { siteId: never contractId: never rest: never + redirectTo: never extra: never name: never str: never diff --git a/packages/vite-plugin-kit-routes/src/lib/ast.ts b/packages/vite-plugin-kit-routes/src/lib/ast.ts new file mode 100644 index 000000000..3e1c1c7d7 --- /dev/null +++ b/packages/vite-plugin-kit-routes/src/lib/ast.ts @@ -0,0 +1,102 @@ +import { parse } from '@babel/parser' +import { cyan, red, yellow } from '@kitql/helpers' +import * as recast from 'recast' + +import { read } from './fs.js' +import { log, routes_path } from './plugin.js' + +const { visit } = recast.types + +export const getMethodsOfServerFiles = (pathFile: string) => { + const code = read(`${routes_path()}/${pathFile}/${'+server.ts'}`) + + const codeParsed = parse(code ?? '', { + plugins: ['typescript', 'importAssertions', 'decorators-legacy'], + sourceType: 'module', + }).program as recast.types.namedTypes.Program + + let exportedNames: string[] = [] + visit(codeParsed, { + visitExportNamedDeclaration(path) { + // @ts-ignore + const declarations = path.node.declaration?.declarations + if (declarations) { + declarations.forEach((declaration: any) => { + if (declaration.id.name) { + exportedNames.push(declaration.id.name) + } + }) + } + + // Check for export specifiers (for aliased exports) + const specifiers = path.node.specifiers + if (specifiers) { + specifiers.forEach((specifier: any) => { + if (specifier.exported.name) { + exportedNames.push(specifier.exported.name) + } + }) + } + + return false + }, + }) + + return exportedNames +} + +export const getActionsOfServerPages = (pathFile: string) => { + const pathToFile = `${pathFile}/+page.server.ts` + const code = read(`${routes_path()}/${pathFile}/${'+page.server.ts'}`) + + let withLoad = false + + const codeParsed = parse(code ?? '', { + plugins: ['typescript', 'importAssertions', 'decorators-legacy'], + sourceType: 'module', + }).program as recast.types.namedTypes.Program + + let actions: string[] = [] + visit(codeParsed, { + visitExportNamedDeclaration(path) { + // @ts-ignore + const declarations = path.node.declaration?.declarations + if (declarations) { + declarations.forEach((declaration: any) => { + if (declaration.id.name === 'actions') { + const properties = + // if } satisfies Actions + declaration.init.expression?.properties ?? + // if no satisfies Actions + declaration.init.properties + + if (properties) { + properties.forEach((property: any) => { + actions.push(property.key.name) + }) + } + } + if (declaration.id.name === 'load') { + withLoad = true + } + }) + } + return false + }, + }) + + if (actions.length > 1 && actions.includes('default')) { + // Let's remove the default action form our list, and say something + actions = actions.filter(c => c !== 'default') + log.error( + `In file: ${yellow(pathToFile)}` + + `\n\t When using named actions (${yellow(actions.join(', '))})` + + `, the ${red('default')} action cannot be used. ` + + `\n\t See the docs for more info: ` + + `${cyan(`https://kit.svelte.dev/docs/form-actions#named-actions`)}`, + ) + } + + // TODO: withLoad to be used one day? with PAGE_SERVER_LOAD? PAGE_LOAD? + return { actions, withLoad } +} diff --git a/packages/vite-plugin-kit-routes/src/lib/format.ts b/packages/vite-plugin-kit-routes/src/lib/format.ts new file mode 100644 index 000000000..f45a5dd06 --- /dev/null +++ b/packages/vite-plugin-kit-routes/src/lib/format.ts @@ -0,0 +1,63 @@ +export const format = (margin: { left?: number; top?: number; bottom?: number }, str: string) => { + const m = { + left: margin.left ?? 2, + top: margin.top ?? 0, + bottom: margin.bottom ?? 1, + } + + if (str === '') return '' + + const strWithSpace = str + .split('\n') + .map(c => `${Array(m.left).fill(' ').join('')}${c}`) + .join('\n') + + return ( + `${Array(m.top).fill('\n').join('')}` + + `${strWithSpace}` + + `${Array(m.bottom).fill('\n').join('')}` + ) +} + +export const appendSp = `/** + * Append search params to a string + */ +const appendSp = (sp?: Record, prefix: '?' | '&' = '?') => { + if (sp === undefined) return '' + const mapping = Object.entries(sp) + .filter(c => c[1] !== undefined) + .map(c => [c[0], String(c[1])]) + + const formated = new URLSearchParams(mapping).toString() + if (formated) { + return \`\${prefix}\${formated}\` + } + return '' +}` + +export const routeFn = `// route function helpers +type NonFunctionKeys = { [K in keyof T]: T[K] extends Function ? never : K }[keyof T] +type FunctionKeys = { [K in keyof T]: T[K] extends Function ? K : never }[keyof T] +type FunctionParams = T extends (...args: infer P) => any ? P : never + +const AllObjs = { ...PAGES, ...ACTIONS, ...SERVERS, ...LINKS } +type AllTypes = typeof AllObjs + +/** + * To be used like this: + * \`\`\`ts + * import { route } from '$lib/ROUTES' + * + * route('site_id', { id: 1 }) + * \`\`\` + */ +export function route>(key: T, ...params: FunctionParams): string +export function route>(key: T): string +export function route(key: T, ...params: any[]): string { + if (AllObjs[key] instanceof Function) { + const element = (AllObjs as any)[key] as (...args: any[]) => string + return element(...params) + } else { + return AllObjs[key] as string + } +}` diff --git a/packages/vite-plugin-kit-routes/src/lib/fs.spec.ts b/packages/vite-plugin-kit-routes/src/lib/fs.spec.ts index f0f1418d8..1c34c1e09 100644 --- a/packages/vite-plugin-kit-routes/src/lib/fs.spec.ts +++ b/packages/vite-plugin-kit-routes/src/lib/fs.spec.ts @@ -1,6 +1,7 @@ import { describe, expect, it } from 'vitest' import { getFilesUnder } from './fs.js' +import { rmvGroups, rmvOptional } from './plugin.js' describe('fs', () => { it('getFilesUnder', async () => { @@ -22,6 +23,7 @@ describe('fs', () => { "[[lang]]/site/+page.server.ts", "[[lang]]/site/+page.svelte", "[[lang]]/site/+server.ts", + "[[lang]]/site/[id]/+page.server.ts", "[[lang]]/site/[id]/+page.svelte", "[[lang]]/site_contract/+page.server.ts", "[[lang]]/site_contract/[siteId]-[contractId]/+page.server.ts", @@ -38,3 +40,41 @@ describe('fs', () => { `) }) }) + +describe('rmvOptional', () => { + it('getFilesUnder', async () => { + const location = `${process.cwd()}/src/routes/` + expect(getFilesUnder(location).map(c => rmvOptional(rmvGroups(c)))).toMatchInlineSnapshot(` + [ + "/subGroup/+page.svelte", + "/subGroup2/+page.svelte", + "+layout.svelte", + "+page.svelte", + "/contract/+page.svelte", + "/contract/+server.ts", + "/contract/[id]/+page.server.ts", + "/contract/[id]/+page.svelte", + "/gp/one/+page.svelte", + "/gp/two/+page.svelte", + "/main/+page.svelte", + "/match/[id=int]/+page.svelte", + "/site/+page.server.ts", + "/site/+page.svelte", + "/site/+server.ts", + "/site/[id]/+page.server.ts", + "/site/[id]/+page.svelte", + "/site_contract/+page.server.ts", + "/site_contract/[siteId]-[contractId]/+page.server.ts", + "/site_contract/[siteId]-[contractId]/+page.svelte", + "a/[...rest]/z/+page.svelte", + "api/graphql/+server.ts", + "lay/+layout.svelte", + "lay/normal/+page.svelte", + "lay/root-layout/+page@.svelte", + "lay/skip/+page@lay.svelte", + "lay/+layout.svelte", + "page_server_woAction/+page.server.ts", + ] + `) + }) +}) diff --git a/packages/vite-plugin-kit-routes/src/lib/plugin.ts b/packages/vite-plugin-kit-routes/src/lib/plugin.ts index 0dcca6caf..42965114e 100644 --- a/packages/vite-plugin-kit-routes/src/lib/plugin.ts +++ b/packages/vite-plugin-kit-routes/src/lib/plugin.ts @@ -1,13 +1,11 @@ -import { parse } from '@babel/parser' -import { cyan, green, Log, red, yellow } from '@kitql/helpers' +import { cyan, gray, green, italic, Log, red, stry0, yellow } from '@kitql/helpers' import { spawn } from 'child_process' -import * as recast from 'recast' import type { Plugin } from 'vite' import watch_and_run from 'vite-plugin-watch-and-run' -import { getFilesUnder, read, write } from './fs.js' - -const { visit } = recast.types +import { getActionsOfServerPages, getMethodsOfServerFiles } from './ast.js' +import { appendSp, format, routeFn } from './format.js' +import { getFilesUnder, write } from './fs.js' type ExtendTypes = { PAGES: Record @@ -16,7 +14,8 @@ type ExtendTypes = { Params: Record } -type LogKind = 'update' | 'post_update_run' | 'errors' +type LogKind = 'update' | 'post_update_run' | 'errors' | 'stats' +type FormatKind = 'route(path)' | 'route(symbol)' | 'variables' | 'object[path]' | 'object[symbol]' export type Options = { /** @@ -30,10 +29,17 @@ export type Options = { post_update_run?: string /** - * by default, everything is logged. If you want to remove them, send an empty array. - * If you want only `update` logs, give `['update']` + * by default, everything is logged. If you want to remove some, add them in the array. + * + * `update` when the file is updated + * + * `post_update_run` to log the command you run + * + * `errors` in case you have some! + * + * `stats` to have some stats about your routes & co 🥳 */ - logs?: LogKind[] + exclude_logs?: LogKind[] /** * @default 'src/lib/ROUTES.ts' @@ -42,14 +48,38 @@ export type Options = { /** * ```ts - * // when `/` (default) you can use: - * PAGES["/site/[id]/two/[hello]"] + * // format: route(path) -> default <- + * route("/site/[id]", { id: 7, tab: 'info' }) + * + * // format: route(symbol) + * route("site_id", { id: 7, tab: 'info' }) + * + * // format: `variables` (best for code splitting & privacy) + * PAGE_site_id({ id: 7, tab: 'info' }) * - * // when `_` you can use: - * PAGES.site_id_two_hello + * // format: object[path] + * PAGES["/site/[id]"]({ id: 7, tab: 'info' }) + * + * // format: object[symbol] + * PAGES.site_id({ id: 7, tab: 'info' }) * ``` */ - format?: '/' | '_' | 'variables' + format?: FormatKind + + // /** + // * default is: `false` + // * + // * If you have only 1 required param, it will be the seond param. + // * + // * ```ts + // * route("/site/[id]", 7) + // * route("site_id", 7) + // * PAGE_site_id(7) + // * PAGES["/site/[id]"](7) + // * PAGES.site_id(7) + // * ``` + // */ + // params_always_as_object?: boolean /** * default is: `string | number` @@ -180,8 +210,10 @@ export type ExplicitSearchParam = ExtendParam & { required?: boolean } -function generated_file_path(options?: Options) { - return options?.generated_file_path ?? 'src/lib/ROUTES.ts' +export const log = new Log('Kit Routes') + +export function routes_path() { + return `${process.cwd()}/src/routes` } export function rmvGroups(key: string) { @@ -192,10 +224,18 @@ export function rmvGroups(key: string) { return toRet } -export function formatKey(key: string, options?: Options) { - let toRet = rmvGroups(key) +export function rmvOptional(key: string) { + let toRet = key + // rmv (Optional) + .replace(/\[{2}.*?\]{2}/g, '') + return toRet +} + +export function formatKey(key: string, o: Options) { + const options = getDefaultOption(o) + let toRet = rmvGroups(rmvOptional(key)) - if (options?.format === undefined || options?.format === '/') { + if (options.format!.includes('path')) { return toRet } @@ -222,32 +262,28 @@ export function formatKey(key: string, options?: Options) { return toRet } -function routes_path() { - return `${process.cwd()}/src/routes` +type MetadataToWrite = { + keyToUse: string + key_wo_prefix: string + // prop: string + paramsFromPath: Param[] + strDefault: string + strReturn: string + strParams: string } -// const routes_path = 'src/lib/ROUTES.ts' -const log = new Log('Kit Routes') +type KindOfObject = 'PAGES' | 'SERVERS' | 'ACTIONS' | 'LINKS' -const getFileKeys = ( - files: string[], - type: 'PAGES' | 'SERVERS' | 'ACTIONS' | 'LINKS', - options?: Options, - withAppendSp?: boolean, -) => { +const getMetadata = (files: string[], type: KindOfObject, o: Options, withAppendSp?: boolean) => { + const options = getDefaultOption(o) const useWithAppendSp = withAppendSp && options?.extra_search_params === 'with' if (type === 'LINKS') { - const toRet = Object.entries(options?.LINKS ?? {}).map(c => { + const toRet = Object.entries(options?.LINKS ?? {}).flatMap(c => { const hrefToUse = typeof c[1] === 'string' ? c[1] : c[1].href - - return fileToMetadata(c[0], hrefToUse, type, options, useWithAppendSp) + return transformToMetadata(c[0], hrefToUse, type, options, useWithAppendSp) }) - return toRet.filter(c => c !== null) as { - keyToUse: string - prop: string - paramsFromPath: Param[] - }[] + return toRet.filter(c => c !== null) as MetadataToWrite[] } const lookFor = @@ -264,13 +300,9 @@ const getFileKeys = ( .map(file => `/` + file.replace(`/${lookFor}`, '').replace(lookFor, '')) // Keep the sorting at this level, it will make more sense .sort() - .map(original => fileToMetadata(original, original, type, options, useWithAppendSp)) + .flatMap(original => transformToMetadata(original, original, type, options, useWithAppendSp)) - return toRet.filter(c => c !== null) as { - keyToUse: string - prop: string - paramsFromPath: Param[] - }[] + return toRet.filter(c => c !== null) as MetadataToWrite[] } type Param = { @@ -283,16 +315,107 @@ type Param = { isArray: boolean } -export const fileToMetadata = ( +export const transformToMetadata = ( original: string, originalValue: string, - type: 'PAGES' | 'SERVERS' | 'ACTIONS' | 'LINKS', - options: Options | undefined, + type: KindOfObject, + options: Options, useWithAppendSp: boolean | undefined, -) => { +): MetadataToWrite[] => { const keyToUse = formatKey(original, options) let toRet = rmvGroups(originalValue) + const list: MetadataToWrite[] = [] + + const getSep = () => { + return options?.format?.includes('route') || options?.format?.includes('path') ? ` ` : `_` + } + + if (type === 'ACTIONS') { + const { actions } = getActionsOfServerPages(originalValue) + if (actions.length === 0) { + } else if (actions.length === 1 && actions[0] === 'default') { + list.push( + buildMetadata( + type, + originalValue, + 'default' + getSep() + keyToUse, + keyToUse, + + useWithAppendSp, + '', + toRet, + options, + ), + ) + } else { + actions.map(action => { + list.push( + buildMetadata( + type, + originalValue, + action + getSep() + keyToUse, + keyToUse, + + useWithAppendSp, + `?/${action}`, + toRet, + options, + ), + ) + }) + } + } else if (type === 'SERVERS') { + const methods = getMethodsOfServerFiles(originalValue) + if (methods.length === 0) { + return [] + } else { + methods.map(method => { + list.push( + buildMetadata( + type, + originalValue, + method + getSep() + keyToUse, + keyToUse, + + useWithAppendSp, + ``, + toRet, + options, + ), + ) + }) + } + } else { + list.push( + buildMetadata( + type, + originalValue, + keyToUse, + keyToUse, + + useWithAppendSp, + '', + toRet, + options, + ), + ) + } + + return list +} + +export function buildMetadata( + type: KindOfObject, + originalValue: string, + keyToUse: string, + key_wo_prefix: string, + useWithAppendSp: boolean | undefined, + actionsFormat: string, + toRet: string, + o: Options, +) { + const options = getDefaultOption(o) // custom conf const viteCustomPathConfig = options?.[type] let customConf: CustomPath = { @@ -357,24 +480,6 @@ export const fileToMetadata = ( const params = [] - let actionsFormat = '' - if (type === 'SERVERS') { - const methods = getMethodsOfServerFiles(originalValue) - if (methods.length > 0) { - params.push(`method: ${methods.map(c => `'${c}'`).join(' | ')}`) - } - } else if (type === 'ACTIONS') { - const { actions } = getActionsOfServerPages(originalValue) - - if (actions.length === 0) { - return null - } else if (actions.length === 1 && actions[0] === 'default') { - } else { - params.push(`action: ${actions.map(c => `'${c}'`).join(' | ')}`) - actionsFormat = `?/\${action}` - } - } - // custom search Param? const explicit_search_params_to_function: string[] = [] if (customConf.explicit_search_params) { @@ -386,15 +491,13 @@ export const fileToMetadata = ( default: sp[1].default, isArray: false, }) - explicit_search_params_to_function.push(`${sp[0]}: params.${sp[0]}`) + explicit_search_params_to_function.push(`${sp[0]}: params?.${sp[0]}`) }) } + const isAllOptional = paramsFromPath.filter(c => !c.optional).length === 0 if (paramsFromPath.length > 0) { - const isAllOptional = paramsFromPath.filter(c => !c.optional).length === 0 - params.push( - `params: {${formatArgs(paramsFromPath, options)}}` + `${isAllOptional ? '= {}' : ''}`, - ) + params.push(`params${isAllOptional ? '?' : ''}: { ${formatArgs(paramsFromPath, options)} }`) } let fullSP = '' @@ -408,9 +511,9 @@ export const fileToMetadata = ( fullSP = `\${appendSp(sp${appendSpPrefix})}` } else if (wExtraSP && customConf.explicit_search_params) { params.push(`sp?: Record`) - fullSP = `\${appendSp({...sp, ${explicit_search_params_to_function.join( - ', ', - )} }${appendSpPrefix})}` + fullSP = + `\${appendSp({ ${explicit_search_params_to_function.join(', ')}` + + `, ...sp }${appendSpPrefix})}` } else if (!wExtraSP && customConf.explicit_search_params) { fullSP = `\${appendSp({ ${explicit_search_params_to_function.join(', ')} }${appendSpPrefix})}` } @@ -421,20 +524,26 @@ export const fileToMetadata = ( return `params.${c.name} = params.${c.name} ?? ${c.default}; ` }) - const pathBaesStr = options?.path_base ? '${base}' : '' + if (paramsDefaults.length > 0 && isAllOptional) { + paramsDefaults = ['params = params ?? {}', ...paramsDefaults] + } - let prop = '' - if (params.length > 0) { - prop = - `"${keyToUse}": (${params.join(', ')}) => ` + - ` {${paramsDefaults.length > 0 ? `\n ${paramsDefaults.join('\n ')}` : ''} - return \`${pathBaesStr}${toRet}${actionsFormat}${fullSP}\` - }` - } else { - prop = `"${keyToUse}": \`${pathBaesStr}${toRet}\`` + const pathBaesStr = options?.path_base ? '${base}' : '' + const strDefault = paramsDefaults.length > 0 ? `${paramsDefaults.join('\n')}` : '' + const strReturn = `\`${pathBaesStr}${toRet}${actionsFormat}${fullSP}\`` + const strParams = params.join(', ') + + const baseToReturn: MetadataToWrite = { + keyToUse, + key_wo_prefix, + // prop, + paramsFromPath, + strDefault, + strReturn, + strParams, } - return { keyToUse, prop, paramsFromPath } + return baseToReturn } export function extractParamsFromPath(path: string): Param[] { @@ -468,8 +577,18 @@ export function extractParamsFromPath(path: string): Param[] { return params } -const formatArgs = (params: Param[], options?: Options) => { +const formatArgs = (params: Param[], o: Options) => { + const options = getDefaultOption(o) return params + .sort((a, b) => { + // if (a.optional === b.optional) { + // // When 'optional' is the same, sort by 'name' + // return a.name < b.name ? -1 : a.name > b.name ? 1 : 0 + // } + // Otherwise, sort by 'optional' + // Let's sort only by 'optional' at the end. + return a.optional < b.optional ? -1 : 1 + }) .map(c => { const override_params = Object.entries(options?.override_params ?? {}).filter( d => d[0] === c.name, @@ -489,106 +608,22 @@ const formatArgs = (params: Param[], options?: Options) => { .join(', ') } -const getMethodsOfServerFiles = (path: string) => { - const code = read(`${routes_path()}/${path}/${'+server.ts'}`) - - const codeParsed = parse(code ?? '', { - plugins: ['typescript', 'importAssertions', 'decorators-legacy'], - sourceType: 'module', - }).program as recast.types.namedTypes.Program - - let exportedNames: string[] = [] - visit(codeParsed, { - visitExportNamedDeclaration(path) { - // @ts-ignore - const declarations = path.node.declaration?.declarations - if (declarations) { - declarations.forEach((declaration: any) => { - if (declaration.id.name) { - exportedNames.push(declaration.id.name) - } - }) - } - - // Check for export specifiers (for aliased exports) - const specifiers = path.node.specifiers - if (specifiers) { - specifiers.forEach((specifier: any) => { - if (specifier.exported.name) { - exportedNames.push(specifier.exported.name) - } - }) - } - - return false - }, - }) - - return exportedNames -} +const shouldLog = (kind: LogKind, o: Options) => { + const options = getDefaultOption(o) -const getActionsOfServerPages = (pathFile: string) => { - const pathToFile = `${pathFile}/+page.server.ts` - const code = read(`${routes_path()}/${pathFile}/${'+page.server.ts'}`) - - let withLoad = false - - const codeParsed = parse(code ?? '', { - plugins: ['typescript', 'importAssertions', 'decorators-legacy'], - sourceType: 'module', - }).program as recast.types.namedTypes.Program - - let actions: string[] = [] - visit(codeParsed, { - visitExportNamedDeclaration(path) { - // @ts-ignore - const declarations = path.node.declaration?.declarations - if (declarations) { - declarations.forEach((declaration: any) => { - if (declaration.id.name === 'actions') { - const properties = - // if } satisfies Actions - declaration.init.expression?.properties ?? - // if no satisfies Actions - declaration.init.properties - - if (properties) { - properties.forEach((property: any) => { - actions.push(property.key.name) - }) - } - } - if (declaration.id.name === 'load') { - withLoad = true - } - }) - } - return false - }, - }) - - if (actions.length > 1 && actions.includes('default')) { - // Let's remove the default action form our list, and say something - actions = actions.filter(c => c !== 'default') - log.error( - `In file: ${yellow(pathToFile)}` + - `\n\t When using named actions (${yellow(actions.join(', '))})` + - `, the ${red('default')} action cannot be used. ` + - `\n\t See the docs for more info: ` + - `${cyan(`https://kit.svelte.dev/docs/form-actions#named-actions`)}`, - ) + if (o.exclude_logs?.includes(kind)) { + return false } - - // TODO: withLoad to be used one day? with PAGE_SERVER_LOAD? PAGE_LOAD? - return { actions, withLoad } + return true } -const shouldLog = (kind: LogKind, options?: Options) => { - if (options?.logs === undefined) { - // let's log everything by default - return true +export const getDefaultOption = (o?: Options) => { + const options = { + ...o, + format: o?.format ?? 'route(path)', + generated_file_path: o?.generated_file_path ?? 'src/lib/ROUTES.ts', } - return options.logs.includes(kind) + return options } const arrayToRecord = (arr?: string[]) => { @@ -598,7 +633,9 @@ const arrayToRecord = (arr?: string[]) => { return `: Record` } -export const run = (options?: Options) => { +export const run = (o?: Options) => { + const options = getDefaultOption(o) + let files = getFilesUnder(routes_path()) // TODO check if harcoded links are around? @@ -606,12 +643,12 @@ export const run = (options?: Options) => { // goto, href, action, src, throw redirect? // } - const objTypes = [ - { type: 'PAGES', files: getFileKeys(files, 'PAGES', options, true) }, - { type: 'SERVERS', files: getFileKeys(files, 'SERVERS', options, true) }, - { type: 'ACTIONS', files: getFileKeys(files, 'ACTIONS', options, false) }, - { type: 'LINKS', files: getFileKeys(files, 'LINKS', options, false) }, - ] as const + const objTypes: { type: KindOfObject; files: MetadataToWrite[] }[] = [ + { type: 'PAGES', files: getMetadata(files, 'PAGES', options, true) }, + { type: 'SERVERS', files: getMetadata(files, 'SERVERS', options, true) }, + { type: 'ACTIONS', files: getMetadata(files, 'ACTIONS', options, false) }, + { type: 'LINKS', files: getMetadata(files, 'LINKS', options, false) }, + ] // Validate options let allOk = true @@ -627,7 +664,8 @@ export const run = (options?: Options) => { `Can't extend "${green(`${o.type}.`)}${red(key)}" as this path doesn't exist!`, ) } - allOk = false + // Even with warning, we should wite the file + // allOk = false } else { if (cPath) { Object.entries(cPath.params ?? {}).forEach(p => { @@ -641,7 +679,8 @@ export const run = (options?: Options) => { )}" as this param doesn't exist!`, ) } - allOk = false + // Even with warning, we should wite the file + // allOk = false } }) } @@ -650,7 +689,7 @@ export const run = (options?: Options) => { }) if (allOk) { - const result = write(generated_file_path(options), [ + const result = write(options.generated_file_path, [ `/** * This file was generated by 'vite-plugin-kit-routes' * @@ -659,39 +698,58 @@ export const run = (options?: Options) => { `, // consts options?.format === 'variables' - ? objTypes + ? // Format variables + objTypes .map(c => { - return c.files - .map(key => { - const [first, ...rest] = key.prop.split(':') - - return `export const ${c.type}_${first.slice(1, -1)} = ${rest.join(':')}` - }) - .join('\n') + return `/**\n * ${c.type}\n */ +${c.files + .map(key => { + if (key.strParams) { + return ( + `export const ${c.type.slice(0, -1)}_${key.keyToUse} = (${key.strParams}) => {` + + `${format({ bottom: 0, top: 1, left: 2 }, key.strDefault)} + return ${key.strReturn} +}` + ) + } else { + return `export const ${c.type.slice(0, -1)}_${key.keyToUse} = ${key.strReturn}` + } + }) + .join('\n')}` }) .join(`\n\n`) - : objTypes + : // Format Others + objTypes .map(c => { - return `export const ${c.type} = { - ${c.files.map(key => key.prop).join(',\n ')} + return ( + `/**\n * ${c.type}\n */ +${options?.format?.includes('route') ? `` : `export `}` + + `const ${c.type} = { + ${c.files + .map(key => { + if (key.strParams) { + return ( + `"${key.keyToUse}": (${key.strParams}) => {` + + `${format({ bottom: 0, top: 1, left: 4 }, key.strDefault)} + return ${key.strReturn} + }` + ) + } else { + return `"${key.keyToUse}": ${key.strReturn}` + } + }) + .join(',\n ')} }` + ) }) .join(`\n\n`), - ` -const appendSp = (sp?: Record, prefix: '?' | '&' = '?') => { - if (sp === undefined) return '' - const mapping = Object.entries(sp) - .filter(c => c[1] !== undefined) - .map(c => [c[0], String(c[1])]) - - const formated = new URLSearchParams(mapping).toString() - if (formated) { - return \`\${prefix}\${formated}\` - } - return '' -} -`, // types + format({ top: 1, left: 0 }, appendSp), + + // add appendSp + ...(options?.format?.includes('route') ? [format({ left: 0 }, routeFn)] : []), + + // types `/** * Add this type as a generic of the vite plugin \`kitRoutes\`. * @@ -702,7 +760,7 @@ const appendSp = (sp?: Record, prefix: '?' * * kitRoutes({ * PAGES: { -* // here, "paths" it will be typed! +* // here, key of object will be typed! * } * }) * \`\`\` @@ -762,28 +820,11 @@ ${objTypes if (shouldLog('update', options)) { child.on('close', code => { - if (result) { - log.success(`${yellow(generated_file_path(options))} updated`) - // TODO later - // log.info( - // `⚠️ Warning ${yellow(`href="/about"`)} detected ` + - // `in ${gray('/src/lib/component/menu.svelte')} is not safe. ` + - // `You could use: ${green(`href={PAGES['/about']}`)}`, - // ) - // log.info( - // `⚠️ Warning ${yellow(`action="?/save"`)} detected ` + - // `in ${gray('/routes/card/+page.svelte')} is not safe. ` + - // `You could use: ${green(`href={ACTION['/card']('save')}`)}`, - // ) - } + theEnd(result, objTypes, options) }) } } else { - if (result) { - if (shouldLog('update', options)) { - log.success(`${yellow(generated_file_path(options))} updated`) - } - } + theEnd(result, objTypes, options) } return true } @@ -791,6 +832,63 @@ ${objTypes return false } +function theEnd( + result: boolean, + objTypes: { type: KindOfObject; files: MetadataToWrite[] }[], + o: Options, +) { + const options = getDefaultOption(o) + if (result) { + if (shouldLog('update', options)) { + log.success(`${yellow(options.generated_file_path)} updated`) + } + + if (shouldLog('stats', options)) { + const stats = [] + let nbRoutes = objTypes.flatMap(c => c.files).length + stats.push( + `Routes: ${yellow('' + nbRoutes)} ` + + `${italic( + `(${objTypes.map(c => `${c.type}: ${yellow('' + c.files.length)}`).join(', ')})`, + )}`, + ) + let confgPoints = stry0(Object.entries(options ?? {}))!.length + + stats.push(`Points: ${yellow('' + confgPoints)}`) + const score = (confgPoints / nbRoutes).toFixed(2) + stats.push(`Score: ${yellow(score)}`) + stats.push(`Format: "${yellow('' + options?.format)}"`) + + log.success(`${green('Stats:')} ${stats.join(' | ')}`) + log.info( + `${gray(' Share on TwiX:')} ${cyan( + `https://twitter.com/intent/tweet?text=` + + `${encodeURI(`🚀 Check out my `)}%23${encodeURI( + `KitRoutes stats 🚀\n\n` + + `- Routes: ${nbRoutes} (${objTypes.map(c => c.files.length).join(', ')})\n` + + `- Points: ${confgPoints}\n` + + `- Score: ${score}\n` + + `- Format: "${options?.format}"\n\n` + + `👀 @jycouet`, + )}`, + )}`, + ) + } + + // TODO later + // log.info( + // `⚠️ Warning ${yellow(`href="/about"`)} detected ` + + // `in ${gray('/src/lib/component/menu.svelte')} is not safe. ` + + // `You could use: ${green(`href={PAGES['/about']}`)}`, + // ) + // log.info( + // `⚠️ Warning ${yellow(`action="?/save"`)} detected ` + + // `in ${gray('/routes/card/+page.svelte')} is not safe. ` + + // `You could use: ${green(`href={ACTION['/card']('save')}`)}`, + // ) + } +} + /** * First you can start with something simple: * ```ts diff --git a/packages/vite-plugin-kit-routes/src/lib/plugins.spec.ts b/packages/vite-plugin-kit-routes/src/lib/plugins.spec.ts index cb4575adc..abfd39a0c 100644 --- a/packages/vite-plugin-kit-routes/src/lib/plugins.spec.ts +++ b/packages/vite-plugin-kit-routes/src/lib/plugins.spec.ts @@ -1,7 +1,18 @@ import { describe, expect, it } from 'vitest' +import type { KIT_ROUTES as KIT_ROUTES_ObjectPath } from '../test/ROUTES_format-object-path.js' +import type { KIT_ROUTES as KIT_ROUTES_ObjectSymbol } from '../test/ROUTES_format-object-symbol.js' +import type { KIT_ROUTES as KIT_ROUTES_RoutePath } from '../test/ROUTES_format-route-path.js' +import type { KIT_ROUTES as KIT_ROUTES_RouteSymbol } from '../test/ROUTES_format-route-symbol.js' +import type { KIT_ROUTES as KIT_ROUTES_Variables } from '../test/ROUTES_format-variables.js' import { read } from './fs.js' -import { extractParamsFromPath, fileToMetadata, formatKey, run, type Options } from './plugin.js' +import { + extractParamsFromPath, + transformToMetadata, + formatKey, + run, + type Options, +} from './plugin.js' describe('vite-plugin-kit-routes', () => { it('get id', async () => { @@ -75,49 +86,63 @@ describe('vite-plugin-kit-routes', () => { }) it('formatKey default', async () => { - expect(formatKey('/[param]site/[yop](group)/[id]')).toMatchInlineSnapshot( + expect(formatKey('/[param]site/[yop](group)/[id]', {})).toMatchInlineSnapshot( '"/[param]site/[yop]/[id]"', ) }) it('formatKey /l', async () => { - expect(formatKey('/[param]site/[yop](group)/[id]', { format: '/' })).toMatchInlineSnapshot( - '"/[param]site/[yop]/[id]"', - ) + expect( + formatKey('/[param]site/[yop](group)/[id]', { format: 'object[path]' }), + ).toMatchInlineSnapshot('"/[param]site/[yop]/[id]"') }) it('formatKey _', async () => { - expect(formatKey('/[param]site/[yop](group)/[id]', { format: '_' })).toMatchInlineSnapshot( - '"param_site_yop_id"', - ) + expect( + formatKey('/[param]site/[yop](group)/[id]', { format: 'object[symbol]' }), + ).toMatchInlineSnapshot('"param_site_yop_id"') }) it('formatKey / starting with group', async () => { - expect(formatKey('/(group)/test', { format: '/' })).toMatchInlineSnapshot('"/test"') + expect(formatKey('/(group)/test', { format: 'object[path]' })).toMatchInlineSnapshot('"/test"') }) it('formatKey _ starting with group', async () => { - expect(formatKey('/(group)/test', { format: '_' })).toMatchInlineSnapshot('"test"') + expect(formatKey('/(group)/test', { format: 'object[symbol]' })).toMatchInlineSnapshot('"test"') }) it('formatKey group original', async () => { - expect(formatKey('/[param]site/[yop](group)/[id]', { format: '_' })).toMatchInlineSnapshot( - '"param_site_yop_id"', - ) + expect( + formatKey('/[param]site/[yop](group)/[id]', { format: 'object[symbol]' }), + ).toMatchInlineSnapshot('"param_site_yop_id"') }) it('formatKey ROOT', async () => { - expect(formatKey('/')).toMatchInlineSnapshot('"/"') + expect(formatKey('/', { format: 'object[path]' })).toMatchInlineSnapshot('"/"') }) it('fileToMetadata optional only', async () => { const key = '/[[lang]]' - const meta = fileToMetadata(key, key, 'PAGES', undefined, undefined) + const meta = transformToMetadata(key, key, 'PAGES', {}, undefined) if (meta) { - expect(meta.prop).toMatchInlineSnapshot(` - "\\"/[[lang]]\\": (params: {lang?: (string | number)}= {}) => { - return \`\${params?.lang ? \`/\${params?.lang}\`: '/'}\` - }" + expect(meta).toMatchInlineSnapshot(` + [ + { + "keyToUse": "/", + "key_wo_prefix": "/", + "paramsFromPath": [ + { + "fromPath": true, + "isArray": false, + "name": "lang", + "optional": true, + }, + ], + "strDefault": "", + "strParams": "params?: { lang?: (string | number) }", + "strReturn": "\`\${params?.lang ? \`/\${params?.lang}\`: '/'}\`", + }, + ] `) } else { expect('I should never be').toBe('here') @@ -126,12 +151,26 @@ describe('vite-plugin-kit-routes', () => { it('fileToMetadata optional', async () => { const key = '/[[lang]]/about' - const meta = fileToMetadata(key, key, 'PAGES', undefined, undefined) + const meta = transformToMetadata(key, key, 'PAGES', {}, undefined) if (meta) { - expect(meta.prop).toMatchInlineSnapshot(` - "\\"/[[lang]]/about\\": (params: {lang?: (string | number)}= {}) => { - return \`\${params?.lang ? \`/\${params?.lang}\`: ''}/about\` - }" + expect(meta).toMatchInlineSnapshot(` + [ + { + "keyToUse": "/about", + "key_wo_prefix": "/about", + "paramsFromPath": [ + { + "fromPath": true, + "isArray": false, + "name": "lang", + "optional": true, + }, + ], + "strDefault": "", + "strParams": "params?: { lang?: (string | number) }", + "strReturn": "\`\${params?.lang ? \`/\${params?.lang}\`: ''}/about\`", + }, + ] `) } else { expect('I should never be').toBe('here') @@ -140,12 +179,26 @@ describe('vite-plugin-kit-routes', () => { it('fileToMetadata optional not at start', async () => { const key = '/prefix-[[lang]]/about' - const meta = fileToMetadata(key, key, 'PAGES', undefined, undefined) + const meta = transformToMetadata(key, key, 'PAGES', {}, undefined) if (meta) { - expect(meta.prop).toMatchInlineSnapshot(` - "\\"/prefix-[[lang]]/about\\": (params: {lang?: (string | number)}= {}) => { - return \`/prefix-\${params?.lang ? \`\${params?.lang}\`: ''}/about\` - }" + expect(meta).toMatchInlineSnapshot(` + [ + { + "keyToUse": "/prefix-/about", + "key_wo_prefix": "/prefix-/about", + "paramsFromPath": [ + { + "fromPath": true, + "isArray": false, + "name": "lang", + "optional": true, + }, + ], + "strDefault": "", + "strParams": "params?: { lang?: (string | number) }", + "strReturn": "\`/prefix-\${params?.lang ? \`\${params?.lang}\`: ''}/about\`", + }, + ] `) } else { expect('I should never be').toBe('here') @@ -154,7 +207,7 @@ describe('vite-plugin-kit-routes', () => { it('fileToMetadata default param', async () => { const key = '/subscriptions/[snapshot]/[id]' - const meta = fileToMetadata( + const meta = transformToMetadata( key, key, 'PAGES', @@ -172,10 +225,30 @@ describe('vite-plugin-kit-routes', () => { undefined, ) if (meta) { - expect(meta.prop).toMatchInlineSnapshot(` - "\\"/subscriptions/[snapshot]/[id]\\": (params: {snapshot: (string | number), id: (string | number)}) => { - return \`/subscriptions/\${params.snapshot}/\${params.id}\` - }" + expect(meta).toMatchInlineSnapshot(` + [ + { + "keyToUse": "/subscriptions/[snapshot]/[id]", + "key_wo_prefix": "/subscriptions/[snapshot]/[id]", + "paramsFromPath": [ + { + "fromPath": true, + "isArray": false, + "name": "snapshot", + "optional": false, + }, + { + "fromPath": true, + "isArray": false, + "name": "id", + "optional": false, + }, + ], + "strDefault": "", + "strParams": "params: { snapshot: (string | number), id: (string | number) }", + "strReturn": "\`/subscriptions/\${params.snapshot}/\${params.id}\`", + }, + ] `) } else { expect('I should never be').toBe('here') @@ -183,7 +256,7 @@ describe('vite-plugin-kit-routes', () => { }) }) -describe('run()', () => { +describe('run()', async () => { const commonConfig: Options = { LINKS: { // reference to a hardcoded link @@ -206,682 +279,225 @@ describe('run()', () => { }, } - it('style _', () => { - const generated_file_path = 'src/test/ROUTES_test1.ts' - run({ - generated_file_path, - format: '_', - PAGES: { - subGroup2: { - explicit_search_params: { - first: { - required: true, - }, - }, - }, - lang_contract: { - extra_search_params: 'with', - }, - lang_site: { - explicit_search_params: { limit: { type: 'number' } }, - params: { - // yop: { type: 'number' }, + const commonConfig_symbol: Options = { + PAGES: { + subGroup2: { + explicit_search_params: { + first: { + required: true, }, - extra_search_params: 'with', }, - lang_site_id: { - explicit_search_params: { limit: { type: 'number' }, demo: { type: 'string' } }, - params: { - id: { type: 'string', default: '"Vienna"' }, - lang: { type: "'fr' | 'hu' | undefined", default: '"fr"' }, - }, + }, + contract: { + extra_search_params: 'with', + }, + site: { + explicit_search_params: { limit: { type: 'number' } }, + params: { + // yop: { type: 'number' }, }, - lang_site_contract_siteId_contractId: { - explicit_search_params: { limit: { type: 'number' } }, + extra_search_params: 'with', + }, + site_id: { + explicit_search_params: { limit: { type: 'number' }, demo: { type: 'string' } }, + params: { + id: { type: 'string', default: '"Vienna"' }, + lang: { type: "'fr' | 'hu' | undefined", default: '"fr"' }, }, }, - SERVERS: { - // site: { - // params: { } - // } - // yop: {}, + site_contract_siteId_contractId: { + explicit_search_params: { limit: { type: 'number' } }, }, - ACTIONS: { - lang_contract_id: { - explicit_search_params: { - limit: { type: 'number' }, - }, + }, + SERVERS: {}, + ACTIONS: { + default_contract_id: { + explicit_search_params: { + limit: { type: 'number' }, }, - lang_site_contract_siteId_contractId: { - explicit_search_params: { - extra: { type: "'A' | 'B'", default: '"A"' }, - }, + }, + send_site_contract_siteId_contractId: { + explicit_search_params: { + extra: { type: "'A' | 'B'", default: '"A"' }, }, }, - ...commonConfig, - }) + }, + } - expect(read(generated_file_path)).toMatchInlineSnapshot(` - "/** - * This file was generated by 'vite-plugin-kit-routes' - * - * >> DO NOT EDIT THIS FILE MANUALLY << - */ - - export const PAGES = { - \\"_ROOT\\": \`/\`, - \\"subGroup\\": \`/subGroup\`, - \\"subGroup2\\": (params: {first: (string | number)}) => { - return \`/subGroup2\${appendSp({ first: params.first })}\` - }, - \\"lang_contract\\": (params: {lang?: ('fr' | 'en' | 'hu' | 'at' | string)}= {}, sp?: Record) => { - return \`\${params?.lang ? \`/\${params?.lang}\`: ''}/contract\${appendSp(sp)}\` - }, - \\"lang_contract_id\\": (params: {lang?: ('fr' | 'en' | 'hu' | 'at' | string), id: (string | number)}) => { - return \`\${params?.lang ? \`/\${params?.lang}\`: ''}/contract/\${params.id}\` - }, - \\"lang_gp_one\\": (params: {lang?: ('fr' | 'en' | 'hu' | 'at' | string)}= {}) => { - return \`\${params?.lang ? \`/\${params?.lang}\`: ''}/gp/one\` - }, - \\"lang_gp_two\\": (params: {lang?: ('fr' | 'en' | 'hu' | 'at' | string)}= {}) => { - return \`\${params?.lang ? \`/\${params?.lang}\`: ''}/gp/two\` - }, - \\"lang_main\\": (params: {lang?: ('fr' | 'en' | 'hu' | 'at' | string)}= {}) => { - return \`\${params?.lang ? \`/\${params?.lang}\`: ''}/main\` - }, - \\"lang_match_id_int\\": (params: {lang?: ('fr' | 'en' | 'hu' | 'at' | string), id: (string | number)}) => { - return \`\${params?.lang ? \`/\${params?.lang}\`: ''}/match/\${params.id}\` - }, - \\"lang_site\\": (params: {lang?: ('fr' | 'en' | 'hu' | 'at' | string), limit?: (number)}= {}, sp?: Record) => { - return \`\${params?.lang ? \`/\${params?.lang}\`: ''}/site\${appendSp({...sp, limit: params.limit })}\` - }, - \\"lang_site_id\\": (params: {lang?: ('fr' | 'hu' | undefined), id?: (string), limit?: (number), demo?: (string)}= {}) => { - params.lang = params.lang ?? \\"fr\\"; - params.id = params.id ?? \\"Vienna\\"; - return \`\${params?.lang ? \`/\${params?.lang}\`: ''}/site/\${params.id}\${appendSp({ limit: params.limit, demo: params.demo })}\` - }, - \\"lang_site_contract_siteId_contractId\\": (params: {lang?: ('fr' | 'en' | 'hu' | 'at' | string), siteId: (string | number), contractId: (string | number), limit?: (number)}) => { - return \`\${params?.lang ? \`/\${params?.lang}\`: ''}/site_contract/\${params.siteId}-\${params.contractId}\${appendSp({ limit: params.limit })}\` - }, - \\"a_rest_z\\": (params: {rest: (string | number)[]}) => { - return \`/a/\${params.rest?.join('/')}/z\` - }, - \\"lay_normal\\": \`/lay/normal\`, - \\"lay_root_layout\\": \`/lay/root-layout\`, - \\"lay_skip\\": \`/lay/skip\` - } - - export const SERVERS = { - \\"lang_contract\\": (method: 'GET' | 'POST', params: {lang?: ('fr' | 'en' | 'hu' | 'at' | string)}= {}) => { - return \`\${params?.lang ? \`/\${params?.lang}\`: ''}/contract\` - }, - \\"lang_site\\": (method: 'GET', params: {lang?: ('fr' | 'en' | 'hu' | 'at' | string)}= {}) => { - return \`\${params?.lang ? \`/\${params?.lang}\`: ''}/site\` - }, - \\"api_graphql\\": (method: 'GET' | 'POST') => { - return \`/api/graphql\` - } - } - - export const ACTIONS = { - \\"lang_contract_id\\": (params: {lang?: ('fr' | 'en' | 'hu' | 'at' | string), id: (string | number), limit?: (number)}) => { - return \`\${params?.lang ? \`/\${params?.lang}\`: ''}/contract/\${params.id}\${appendSp({ limit: params.limit })}\` - }, - \\"lang_site\\": (action: 'action1' | 'action2', params: {lang?: ('fr' | 'en' | 'hu' | 'at' | string)}= {}) => { - return \`\${params?.lang ? \`/\${params?.lang}\`: ''}/site?/\${action}\` - }, - \\"lang_site_contract\\": (action: 'noSatisfies', params: {lang?: ('fr' | 'en' | 'hu' | 'at' | string)}= {}) => { - return \`\${params?.lang ? \`/\${params?.lang}\`: ''}/site_contract?/\${action}\` - }, - \\"lang_site_contract_siteId_contractId\\": (action: 'sendSomething', params: {lang?: ('fr' | 'en' | 'hu' | 'at' | string), siteId: (string | number), contractId: (string | number), extra?: ('A' | 'B')}) => { - params.extra = params.extra ?? \\"A\\"; - return \`\${params?.lang ? \`/\${params?.lang}\`: ''}/site_contract/\${params.siteId}-\${params.contractId}?/\${action}\${appendSp({ extra: params.extra }, '&')}\` - } - } - - export const LINKS = { - \\"twitter\\": \`https:/twitter.com/jycouet\`, - \\"twitter_post\\": (params: {name: (string | number), id: (string | number)}) => { - return \`https:/twitter.com/\${params.name}/status/\${params.id}\` - }, - \\"gravatar\\": (params: {str: (string | number), s?: (number), d?: (\\"retro\\" | \\"identicon\\")}) => { - params.s = params.s ?? 75; - params.d = params.d ?? \\"identicon\\"; - return \`https:/www.gravatar.com/avatar/\${params.str}\${appendSp({ s: params.s, d: params.d })}\` - } - } - - const appendSp = (sp?: Record, prefix: '?' | '&' = '?') => { - if (sp === undefined) return '' - const mapping = Object.entries(sp) - .filter(c => c[1] !== undefined) - .map(c => [c[0], String(c[1])]) - - const formated = new URLSearchParams(mapping).toString() - if (formated) { - return \`\${prefix}\${formated}\` - } - return '' - } - - - /** - * Add this type as a generic of the vite plugin \`kitRoutes\`. - * - * Full example: - * \`\`\`ts - * import type { KIT_ROUTES } from '$lib/ROUTES' - * import { kitRoutes } from 'vite-plugin-kit-routes' - * - * kitRoutes({ - * PAGES: { - * // here, \\"paths\\" it will be typed! - * } - * }) - * \`\`\` - */ - export type KIT_ROUTES = { - PAGES: { '_ROOT': never, 'subGroup': never, 'subGroup2': never, 'lang_contract': 'lang', 'lang_contract_id': 'lang' | 'id', 'lang_gp_one': 'lang', 'lang_gp_two': 'lang', 'lang_main': 'lang', 'lang_match_id_int': 'lang' | 'id', 'lang_site': 'lang', 'lang_site_id': 'lang' | 'id', 'lang_site_contract_siteId_contractId': 'lang' | 'siteId' | 'contractId', 'a_rest_z': 'rest', 'lay_normal': never, 'lay_root_layout': never, 'lay_skip': never } - SERVERS: { 'lang_contract': 'lang', 'lang_site': 'lang', 'api_graphql': never } - ACTIONS: { 'lang_contract_id': 'lang' | 'id', 'lang_site': 'lang', 'lang_site_contract': 'lang', 'lang_site_contract_siteId_contractId': 'lang' | 'siteId' | 'contractId' } - LINKS: { 'twitter': never, 'twitter_post': 'name' | 'id', 'gravatar': 'str' } - Params: { first: never, lang: never, id: never, limit: never, demo: never, siteId: never, contractId: never, rest: never, extra: never, name: never, str: never, s: never, d: never } - } - " - `) + const commonConfig_Path: Options = { + PAGES: { + '/subGroup2': commonConfig_symbol.PAGES?.subGroup2, + '/contract': commonConfig_symbol.PAGES?.contract, + '/site': commonConfig_symbol.PAGES?.site, + '/site/[id]': commonConfig_symbol.PAGES?.site_id, + '/site_contract/[siteId]-[contractId]': + commonConfig_symbol.PAGES?.site_contract_siteId_contractId, + }, + SERVERS: {}, + ACTIONS: { + 'default /contract/[id]': commonConfig_symbol.ACTIONS?.default_contract_id, + 'send /site_contract/[siteId]-[contractId]': + commonConfig_symbol.ACTIONS?.send_site_contract_siteId_contractId, + }, + } + + const commonConfig_symbol_space: Options = { + PAGES: { + subGroup2: commonConfig_symbol.PAGES?.subGroup2, + contract: commonConfig_symbol.PAGES?.contract, + site: commonConfig_symbol.PAGES?.site, + site_id: commonConfig_symbol.PAGES?.site_id, + site_contract_siteId_contractId: commonConfig_symbol.PAGES?.site_contract_siteId_contractId, + }, + SERVERS: {}, + ACTIONS: { + 'default contract_id': commonConfig_symbol.ACTIONS?.default_contract_id, + 'send site_contract_siteId_contractId': + commonConfig_symbol.ACTIONS?.send_site_contract_siteId_contractId, + }, + } + + function fnOrNot(obj: any, key: any, ...params: any[]): string { + if (obj[key] instanceof Function) { + const element = (obj as any)[key] as (...args: any[]) => string + return element(...params) + } else { + return obj[key] as string + } + } + const table = [ + { + name: 'ROOT, return is not a function', + results: '/', + key_path: '/', + key_symbol: '_ROOT', + }, + { + name: 'single param', + results: '/fr/site/Paris', + key_path: '/site/[id]', + key_symbol: 'site_id', + params: { id: 'Paris' }, + }, + ] + + // + // RUN + // + // 'object[path]' + const generated_file_objectPath = 'src/test/ROUTES_format-object-path.ts' + run({ + format: 'object[path]', + generated_file_path: generated_file_objectPath, + ...commonConfig, + ...commonConfig_Path, }) - it('style /', () => { - const generated_file_path = 'src/test/ROUTES_test2.ts' - run({ - generated_file_path, - ...commonConfig, - }) + // 'object[symbol]' + const generated_file_objectSymbol = 'src/test/ROUTES_format-object-symbol.ts' + run({ + generated_file_path: generated_file_objectSymbol, + format: 'object[symbol]', + ...commonConfig_symbol, + ...commonConfig, + }) - expect(read(generated_file_path)).toMatchInlineSnapshot(` - "/** - * This file was generated by 'vite-plugin-kit-routes' - * - * >> DO NOT EDIT THIS FILE MANUALLY << - */ - - export const PAGES = { - \\"/\\": \`/\`, - \\"/subGroup\\": \`/subGroup\`, - \\"/subGroup2\\": \`/subGroup2\`, - \\"/[[lang]]/contract\\": (params: {lang?: ('fr' | 'en' | 'hu' | 'at' | string)}= {}) => { - return \`\${params?.lang ? \`/\${params?.lang}\`: ''}/contract\` - }, - \\"/[[lang]]/contract/[id]\\": (params: {lang?: ('fr' | 'en' | 'hu' | 'at' | string), id: (string | number)}) => { - return \`\${params?.lang ? \`/\${params?.lang}\`: ''}/contract/\${params.id}\` - }, - \\"/[[lang]]/gp/one\\": (params: {lang?: ('fr' | 'en' | 'hu' | 'at' | string)}= {}) => { - return \`\${params?.lang ? \`/\${params?.lang}\`: ''}/gp/one\` - }, - \\"/[[lang]]/gp/two\\": (params: {lang?: ('fr' | 'en' | 'hu' | 'at' | string)}= {}) => { - return \`\${params?.lang ? \`/\${params?.lang}\`: ''}/gp/two\` - }, - \\"/[[lang]]/main\\": (params: {lang?: ('fr' | 'en' | 'hu' | 'at' | string)}= {}) => { - return \`\${params?.lang ? \`/\${params?.lang}\`: ''}/main\` - }, - \\"/[[lang]]/match/[id=int]\\": (params: {lang?: ('fr' | 'en' | 'hu' | 'at' | string), id: (string | number)}) => { - return \`\${params?.lang ? \`/\${params?.lang}\`: ''}/match/\${params.id}\` - }, - \\"/[[lang]]/site\\": (params: {lang?: ('fr' | 'en' | 'hu' | 'at' | string)}= {}) => { - return \`\${params?.lang ? \`/\${params?.lang}\`: ''}/site\` - }, - \\"/[[lang]]/site/[id]\\": (params: {lang?: ('fr' | 'en' | 'hu' | 'at' | string), id: (string | number)}) => { - return \`\${params?.lang ? \`/\${params?.lang}\`: ''}/site/\${params.id}\` - }, - \\"/[[lang]]/site_contract/[siteId]-[contractId]\\": (params: {lang?: ('fr' | 'en' | 'hu' | 'at' | string), siteId: (string | number), contractId: (string | number)}) => { - return \`\${params?.lang ? \`/\${params?.lang}\`: ''}/site_contract/\${params.siteId}-\${params.contractId}\` - }, - \\"/a/[...rest]/z\\": (params: {rest: (string | number)[]}) => { - return \`/a/\${params.rest?.join('/')}/z\` - }, - \\"/lay/normal\\": \`/lay/normal\`, - \\"/lay/root-layout\\": \`/lay/root-layout\`, - \\"/lay/skip\\": \`/lay/skip\` - } - - export const SERVERS = { - \\"/[[lang]]/contract\\": (method: 'GET' | 'POST', params: {lang?: ('fr' | 'en' | 'hu' | 'at' | string)}= {}) => { - return \`\${params?.lang ? \`/\${params?.lang}\`: ''}/contract\` - }, - \\"/[[lang]]/site\\": (method: 'GET', params: {lang?: ('fr' | 'en' | 'hu' | 'at' | string)}= {}) => { - return \`\${params?.lang ? \`/\${params?.lang}\`: ''}/site\` - }, - \\"/api/graphql\\": (method: 'GET' | 'POST') => { - return \`/api/graphql\` - } - } - - export const ACTIONS = { - \\"/[[lang]]/contract/[id]\\": (params: {lang?: ('fr' | 'en' | 'hu' | 'at' | string), id: (string | number)}) => { - return \`\${params?.lang ? \`/\${params?.lang}\`: ''}/contract/\${params.id}\` - }, - \\"/[[lang]]/site\\": (action: 'action1' | 'action2', params: {lang?: ('fr' | 'en' | 'hu' | 'at' | string)}= {}) => { - return \`\${params?.lang ? \`/\${params?.lang}\`: ''}/site?/\${action}\` - }, - \\"/[[lang]]/site_contract\\": (action: 'noSatisfies', params: {lang?: ('fr' | 'en' | 'hu' | 'at' | string)}= {}) => { - return \`\${params?.lang ? \`/\${params?.lang}\`: ''}/site_contract?/\${action}\` - }, - \\"/[[lang]]/site_contract/[siteId]-[contractId]\\": (action: 'sendSomething', params: {lang?: ('fr' | 'en' | 'hu' | 'at' | string), siteId: (string | number), contractId: (string | number)}) => { - return \`\${params?.lang ? \`/\${params?.lang}\`: ''}/site_contract/\${params.siteId}-\${params.contractId}?/\${action}\` - } - } - - export const LINKS = { - \\"twitter\\": \`https:/twitter.com/jycouet\`, - \\"twitter_post\\": (params: {name: (string | number), id: (string | number)}) => { - return \`https:/twitter.com/\${params.name}/status/\${params.id}\` - }, - \\"gravatar\\": (params: {str: (string | number), s?: (number), d?: (\\"retro\\" | \\"identicon\\")}) => { - params.s = params.s ?? 75; - params.d = params.d ?? \\"identicon\\"; - return \`https:/www.gravatar.com/avatar/\${params.str}\${appendSp({ s: params.s, d: params.d })}\` - } - } - - const appendSp = (sp?: Record, prefix: '?' | '&' = '?') => { - if (sp === undefined) return '' - const mapping = Object.entries(sp) - .filter(c => c[1] !== undefined) - .map(c => [c[0], String(c[1])]) - - const formated = new URLSearchParams(mapping).toString() - if (formated) { - return \`\${prefix}\${formated}\` - } - return '' - } - - - /** - * Add this type as a generic of the vite plugin \`kitRoutes\`. - * - * Full example: - * \`\`\`ts - * import type { KIT_ROUTES } from '$lib/ROUTES' - * import { kitRoutes } from 'vite-plugin-kit-routes' - * - * kitRoutes({ - * PAGES: { - * // here, \\"paths\\" it will be typed! - * } - * }) - * \`\`\` - */ - export type KIT_ROUTES = { - PAGES: { '/': never, '/subGroup': never, '/subGroup2': never, '/[[lang]]/contract': 'lang', '/[[lang]]/contract/[id]': 'lang' | 'id', '/[[lang]]/gp/one': 'lang', '/[[lang]]/gp/two': 'lang', '/[[lang]]/main': 'lang', '/[[lang]]/match/[id=int]': 'lang' | 'id', '/[[lang]]/site': 'lang', '/[[lang]]/site/[id]': 'lang' | 'id', '/[[lang]]/site_contract/[siteId]-[contractId]': 'lang' | 'siteId' | 'contractId', '/a/[...rest]/z': 'rest', '/lay/normal': never, '/lay/root-layout': never, '/lay/skip': never } - SERVERS: { '/[[lang]]/contract': 'lang', '/[[lang]]/site': 'lang', '/api/graphql': never } - ACTIONS: { '/[[lang]]/contract/[id]': 'lang' | 'id', '/[[lang]]/site': 'lang', '/[[lang]]/site_contract': 'lang', '/[[lang]]/site_contract/[siteId]-[contractId]': 'lang' | 'siteId' | 'contractId' } - LINKS: { 'twitter': never, 'twitter_post': 'name' | 'id', 'gravatar': 'str' } - Params: { lang: never, id: never, siteId: never, contractId: never, rest: never, name: never, str: never, s: never, d: never } - } - " - `) + // 'route(path)' + const generated_file_routePath = `src/test/ROUTES_format-route-path.ts` + run({ + generated_file_path: generated_file_routePath, + format: 'route(path)', + ...commonConfig, + ...commonConfig_Path, }) - it('style variables', () => { - const generated_file_path = 'src/test/ROUTES_test3.ts' - run({ - generated_file_path, - format: 'variables', - PAGES: { - subGroup2: { - explicit_search_params: { - first: { - required: true, - }, - }, - }, - lang_contract: { - extra_search_params: 'with', - }, - lang_site: { - explicit_search_params: { limit: { type: 'number' } }, - params: { - // yop: { type: 'number' }, - }, - extra_search_params: 'with', - }, - lang_site_id: { - explicit_search_params: { limit: { type: 'number' }, demo: { type: 'string' } }, - params: { - id: { type: 'string', default: '"Vienna"' }, - lang: { type: "'fr' | 'hu' | undefined", default: '"fr"' }, - }, - }, - lang_site_contract_siteId_contractId: { - explicit_search_params: { limit: { type: 'number' } }, - }, - }, - SERVERS: { - // site: { - // params: { } - // } - // yop: {}, - }, - ACTIONS: { - lang_site_contract_siteId_contractId: { - explicit_search_params: { - extra: { type: "'A' | 'B'", default: '"A"' }, - }, - }, - }, - ...commonConfig, - }) + // 'route(symbol)' + const generated_file_routeSymbol = 'src/test/ROUTES_format-route-symbol.ts' + run({ + generated_file_path: generated_file_routeSymbol, + format: 'route(symbol)', + ...commonConfig, + ...commonConfig_symbol_space, + }) - expect(read(generated_file_path)).toMatchInlineSnapshot(` - "/** - * This file was generated by 'vite-plugin-kit-routes' - * - * >> DO NOT EDIT THIS FILE MANUALLY << - */ - - export const PAGES__ROOT = \`/\` - export const PAGES_subGroup = \`/subGroup\` - export const PAGES_subGroup2 = (params: {first: (string | number)}) => { - return \`/subGroup2\${appendSp({ first: params.first })}\` - } - export const PAGES_lang_contract = (params: {lang?: ('fr' | 'en' | 'hu' | 'at' | string)}= {}, sp?: Record) => { - return \`\${params?.lang ? \`/\${params?.lang}\`: ''}/contract\${appendSp(sp)}\` - } - export const PAGES_lang_contract_id = (params: {lang?: ('fr' | 'en' | 'hu' | 'at' | string), id: (string | number)}) => { - return \`\${params?.lang ? \`/\${params?.lang}\`: ''}/contract/\${params.id}\` - } - export const PAGES_lang_gp_one = (params: {lang?: ('fr' | 'en' | 'hu' | 'at' | string)}= {}) => { - return \`\${params?.lang ? \`/\${params?.lang}\`: ''}/gp/one\` - } - export const PAGES_lang_gp_two = (params: {lang?: ('fr' | 'en' | 'hu' | 'at' | string)}= {}) => { - return \`\${params?.lang ? \`/\${params?.lang}\`: ''}/gp/two\` - } - export const PAGES_lang_main = (params: {lang?: ('fr' | 'en' | 'hu' | 'at' | string)}= {}) => { - return \`\${params?.lang ? \`/\${params?.lang}\`: ''}/main\` - } - export const PAGES_lang_match_id_int = (params: {lang?: ('fr' | 'en' | 'hu' | 'at' | string), id: (string | number)}) => { - return \`\${params?.lang ? \`/\${params?.lang}\`: ''}/match/\${params.id}\` - } - export const PAGES_lang_site = (params: {lang?: ('fr' | 'en' | 'hu' | 'at' | string), limit?: (number)}= {}, sp?: Record) => { - return \`\${params?.lang ? \`/\${params?.lang}\`: ''}/site\${appendSp({...sp, limit: params.limit })}\` - } - export const PAGES_lang_site_id = (params: {lang?: ('fr' | 'hu' | undefined), id?: (string), limit?: (number), demo?: (string)}= {}) => { - params.lang = params.lang ?? \\"fr\\"; - params.id = params.id ?? \\"Vienna\\"; - return \`\${params?.lang ? \`/\${params?.lang}\`: ''}/site/\${params.id}\${appendSp({ limit: params.limit, demo: params.demo })}\` - } - export const PAGES_lang_site_contract_siteId_contractId = (params: {lang?: ('fr' | 'en' | 'hu' | 'at' | string), siteId: (string | number), contractId: (string | number), limit?: (number)}) => { - return \`\${params?.lang ? \`/\${params?.lang}\`: ''}/site_contract/\${params.siteId}-\${params.contractId}\${appendSp({ limit: params.limit })}\` - } - export const PAGES_a_rest_z = (params: {rest: (string | number)[]}) => { - return \`/a/\${params.rest?.join('/')}/z\` - } - export const PAGES_lay_normal = \`/lay/normal\` - export const PAGES_lay_root_layout = \`/lay/root-layout\` - export const PAGES_lay_skip = \`/lay/skip\` - - export const SERVERS_lang_contract = (method: 'GET' | 'POST', params: {lang?: ('fr' | 'en' | 'hu' | 'at' | string)}= {}) => { - return \`\${params?.lang ? \`/\${params?.lang}\`: ''}/contract\` - } - export const SERVERS_lang_site = (method: 'GET', params: {lang?: ('fr' | 'en' | 'hu' | 'at' | string)}= {}) => { - return \`\${params?.lang ? \`/\${params?.lang}\`: ''}/site\` - } - export const SERVERS_api_graphql = (method: 'GET' | 'POST') => { - return \`/api/graphql\` - } - - export const ACTIONS_lang_contract_id = (params: {lang?: ('fr' | 'en' | 'hu' | 'at' | string), id: (string | number)}) => { - return \`\${params?.lang ? \`/\${params?.lang}\`: ''}/contract/\${params.id}\` - } - export const ACTIONS_lang_site = (action: 'action1' | 'action2', params: {lang?: ('fr' | 'en' | 'hu' | 'at' | string)}= {}) => { - return \`\${params?.lang ? \`/\${params?.lang}\`: ''}/site?/\${action}\` - } - export const ACTIONS_lang_site_contract = (action: 'noSatisfies', params: {lang?: ('fr' | 'en' | 'hu' | 'at' | string)}= {}) => { - return \`\${params?.lang ? \`/\${params?.lang}\`: ''}/site_contract?/\${action}\` - } - export const ACTIONS_lang_site_contract_siteId_contractId = (action: 'sendSomething', params: {lang?: ('fr' | 'en' | 'hu' | 'at' | string), siteId: (string | number), contractId: (string | number), extra?: ('A' | 'B')}) => { - params.extra = params.extra ?? \\"A\\"; - return \`\${params?.lang ? \`/\${params?.lang}\`: ''}/site_contract/\${params.siteId}-\${params.contractId}?/\${action}\${appendSp({ extra: params.extra }, '&')}\` - } - - export const LINKS_twitter = \`https:/twitter.com/jycouet\` - export const LINKS_twitter_post = (params: {name: (string | number), id: (string | number)}) => { - return \`https:/twitter.com/\${params.name}/status/\${params.id}\` - } - export const LINKS_gravatar = (params: {str: (string | number), s?: (number), d?: (\\"retro\\" | \\"identicon\\")}) => { - params.s = params.s ?? 75; - params.d = params.d ?? \\"identicon\\"; - return \`https:/www.gravatar.com/avatar/\${params.str}\${appendSp({ s: params.s, d: params.d })}\` - } - - const appendSp = (sp?: Record, prefix: '?' | '&' = '?') => { - if (sp === undefined) return '' - const mapping = Object.entries(sp) - .filter(c => c[1] !== undefined) - .map(c => [c[0], String(c[1])]) - - const formated = new URLSearchParams(mapping).toString() - if (formated) { - return \`\${prefix}\${formated}\` - } - return '' - } - - - /** - * Add this type as a generic of the vite plugin \`kitRoutes\`. - * - * Full example: - * \`\`\`ts - * import type { KIT_ROUTES } from '$lib/ROUTES' - * import { kitRoutes } from 'vite-plugin-kit-routes' - * - * kitRoutes({ - * PAGES: { - * // here, \\"paths\\" it will be typed! - * } - * }) - * \`\`\` - */ - export type KIT_ROUTES = { - PAGES: { '_ROOT': never, 'subGroup': never, 'subGroup2': never, 'lang_contract': 'lang', 'lang_contract_id': 'lang' | 'id', 'lang_gp_one': 'lang', 'lang_gp_two': 'lang', 'lang_main': 'lang', 'lang_match_id_int': 'lang' | 'id', 'lang_site': 'lang', 'lang_site_id': 'lang' | 'id', 'lang_site_contract_siteId_contractId': 'lang' | 'siteId' | 'contractId', 'a_rest_z': 'rest', 'lay_normal': never, 'lay_root_layout': never, 'lay_skip': never } - SERVERS: { 'lang_contract': 'lang', 'lang_site': 'lang', 'api_graphql': never } - ACTIONS: { 'lang_contract_id': 'lang' | 'id', 'lang_site': 'lang', 'lang_site_contract': 'lang', 'lang_site_contract_siteId_contractId': 'lang' | 'siteId' | 'contractId' } - LINKS: { 'twitter': never, 'twitter_post': 'name' | 'id', 'gravatar': 'str' } - Params: { first: never, lang: never, id: never, limit: never, demo: never, siteId: never, contractId: never, rest: never, extra: never, name: never, str: never, s: never, d: never } - } - " - `) + // 'variables' + const generated_file_variables = 'src/test/ROUTES_format-variables.ts' + run({ + generated_file_path: generated_file_variables, + format: 'variables', + ...commonConfig_symbol, + ...commonConfig, }) + for (let i = 0; i < table.length; i++) { + const element = table[i] + describe(element.name, async () => { + // + it('object[path]', async () => { + let { PAGES } = await import(generated_file_objectPath) + expect( + fnOrNot(PAGES, element.key_path, element.params), + `Name: ${element.name}, i: ${i}`, + ).toBe(element.results) + }) + + // + it('object[symbol]', async () => { + let { PAGES } = await import(generated_file_objectSymbol) + expect( + fnOrNot(PAGES, element.key_symbol, element.params), + `Name: ${element.name}, i: ${i}`, + ).toBe(element.results) + }) + + // + it('format route(path)', async () => { + let { route } = await import(generated_file_routePath) + expect(route(element.key_path, element.params), `Name: ${element.name}, i: ${i}`).toBe( + element.results, + ) + }) + + // + it('format route(symbol)', async () => { + let { route } = await import(generated_file_routeSymbol) + expect(route(element.key_symbol, element.params), `Name: ${element.name}, i: ${i}`).toBe( + element.results, + ) + }) + + // + it('format variables', async () => { + let vars = await import(generated_file_variables) + if (element.results === '/') { + expect(vars.PAGE__ROOT, `Name: ${element.name}, i: ${i}`).toBe(element.results) + } else if (element.results === '/fr/site/Paris') { + expect(vars.PAGE_site_id({ id: 'Paris' }), `Name: ${element.name}, i: ${i}`).toBe( + element.results, + ) + } + }) + }) + } + it('post_update_run', () => { - const generated_file_path = 'src/test/ROUTES_test4.ts' + const generated_file_path = 'src/test/ROUTES_post-update.ts' run({ generated_file_path, post_update_run: 'echo done', }) + + expect(true).toBe(true) }) it('with path base', () => { - const generated_file_path = 'src/test/ROUTES_test5.ts' + const generated_file_path = 'src/test/ROUTES_base.ts' run({ generated_file_path, - format: '_', path_base: true, - PAGES: { - subGroup2: { - explicit_search_params: { - first: { - required: true, - }, - }, - }, - lang_contract: { - extra_search_params: 'with', - }, - lang_site: { - explicit_search_params: { limit: { type: 'number' } }, - params: { - // yop: { type: 'number' }, - }, - extra_search_params: 'with', - }, - lang_site_id: { - explicit_search_params: { limit: { type: 'number' }, demo: { type: 'string' } }, - params: { - id: { type: 'string', default: '"Vienna"' }, - lang: { type: "'fr' | 'hu' | undefined", default: '"fr"' }, - }, - }, - lang_site_contract_siteId_contractId: { - explicit_search_params: { limit: { type: 'number' } }, - }, - }, - SERVERS: { - // site: { - // params: { } - // } - // yop: {}, - }, - ACTIONS: { - lang_site_contract_siteId_contractId: { - explicit_search_params: { - extra: { type: "'A' | 'B'", default: '"A"' }, - }, - }, - }, - ...commonConfig, }) - expect(read(generated_file_path)).toMatchInlineSnapshot(` - "/** - * This file was generated by 'vite-plugin-kit-routes' - * - * >> DO NOT EDIT THIS FILE MANUALLY << - */ - import { base } from '$app/paths' - - export const PAGES = { - \\"_ROOT\\": \`\${base}/\`, - \\"subGroup\\": \`\${base}/subGroup\`, - \\"subGroup2\\": (params: {first: (string | number)}) => { - return \`\${base}/subGroup2\${appendSp({ first: params.first })}\` - }, - \\"lang_contract\\": (params: {lang?: ('fr' | 'en' | 'hu' | 'at' | string)}= {}, sp?: Record) => { - return \`\${base}\${params?.lang ? \`/\${params?.lang}\`: ''}/contract\${appendSp(sp)}\` - }, - \\"lang_contract_id\\": (params: {lang?: ('fr' | 'en' | 'hu' | 'at' | string), id: (string | number)}) => { - return \`\${base}\${params?.lang ? \`/\${params?.lang}\`: ''}/contract/\${params.id}\` - }, - \\"lang_gp_one\\": (params: {lang?: ('fr' | 'en' | 'hu' | 'at' | string)}= {}) => { - return \`\${base}\${params?.lang ? \`/\${params?.lang}\`: ''}/gp/one\` - }, - \\"lang_gp_two\\": (params: {lang?: ('fr' | 'en' | 'hu' | 'at' | string)}= {}) => { - return \`\${base}\${params?.lang ? \`/\${params?.lang}\`: ''}/gp/two\` - }, - \\"lang_main\\": (params: {lang?: ('fr' | 'en' | 'hu' | 'at' | string)}= {}) => { - return \`\${base}\${params?.lang ? \`/\${params?.lang}\`: ''}/main\` - }, - \\"lang_match_id_int\\": (params: {lang?: ('fr' | 'en' | 'hu' | 'at' | string), id: (string | number)}) => { - return \`\${base}\${params?.lang ? \`/\${params?.lang}\`: ''}/match/\${params.id}\` - }, - \\"lang_site\\": (params: {lang?: ('fr' | 'en' | 'hu' | 'at' | string), limit?: (number)}= {}, sp?: Record) => { - return \`\${base}\${params?.lang ? \`/\${params?.lang}\`: ''}/site\${appendSp({...sp, limit: params.limit })}\` - }, - \\"lang_site_id\\": (params: {lang?: ('fr' | 'hu' | undefined), id?: (string), limit?: (number), demo?: (string)}= {}) => { - params.lang = params.lang ?? \\"fr\\"; - params.id = params.id ?? \\"Vienna\\"; - return \`\${base}\${params?.lang ? \`/\${params?.lang}\`: ''}/site/\${params.id}\${appendSp({ limit: params.limit, demo: params.demo })}\` - }, - \\"lang_site_contract_siteId_contractId\\": (params: {lang?: ('fr' | 'en' | 'hu' | 'at' | string), siteId: (string | number), contractId: (string | number), limit?: (number)}) => { - return \`\${base}\${params?.lang ? \`/\${params?.lang}\`: ''}/site_contract/\${params.siteId}-\${params.contractId}\${appendSp({ limit: params.limit })}\` - }, - \\"a_rest_z\\": (params: {rest: (string | number)[]}) => { - return \`\${base}/a/\${params.rest?.join('/')}/z\` - }, - \\"lay_normal\\": \`\${base}/lay/normal\`, - \\"lay_root_layout\\": \`\${base}/lay/root-layout\`, - \\"lay_skip\\": \`\${base}/lay/skip\` - } - - export const SERVERS = { - \\"lang_contract\\": (method: 'GET' | 'POST', params: {lang?: ('fr' | 'en' | 'hu' | 'at' | string)}= {}) => { - return \`\${base}\${params?.lang ? \`/\${params?.lang}\`: ''}/contract\` - }, - \\"lang_site\\": (method: 'GET', params: {lang?: ('fr' | 'en' | 'hu' | 'at' | string)}= {}) => { - return \`\${base}\${params?.lang ? \`/\${params?.lang}\`: ''}/site\` - }, - \\"api_graphql\\": (method: 'GET' | 'POST') => { - return \`\${base}/api/graphql\` - } - } - - export const ACTIONS = { - \\"lang_contract_id\\": (params: {lang?: ('fr' | 'en' | 'hu' | 'at' | string), id: (string | number)}) => { - return \`\${base}\${params?.lang ? \`/\${params?.lang}\`: ''}/contract/\${params.id}\` - }, - \\"lang_site\\": (action: 'action1' | 'action2', params: {lang?: ('fr' | 'en' | 'hu' | 'at' | string)}= {}) => { - return \`\${base}\${params?.lang ? \`/\${params?.lang}\`: ''}/site?/\${action}\` - }, - \\"lang_site_contract\\": (action: 'noSatisfies', params: {lang?: ('fr' | 'en' | 'hu' | 'at' | string)}= {}) => { - return \`\${base}\${params?.lang ? \`/\${params?.lang}\`: ''}/site_contract?/\${action}\` - }, - \\"lang_site_contract_siteId_contractId\\": (action: 'sendSomething', params: {lang?: ('fr' | 'en' | 'hu' | 'at' | string), siteId: (string | number), contractId: (string | number), extra?: ('A' | 'B')}) => { - params.extra = params.extra ?? \\"A\\"; - return \`\${base}\${params?.lang ? \`/\${params?.lang}\`: ''}/site_contract/\${params.siteId}-\${params.contractId}?/\${action}\${appendSp({ extra: params.extra }, '&')}\` - } - } - - export const LINKS = { - \\"twitter\\": \`\${base}https:/twitter.com/jycouet\`, - \\"twitter_post\\": (params: {name: (string | number), id: (string | number)}) => { - return \`\${base}https:/twitter.com/\${params.name}/status/\${params.id}\` - }, - \\"gravatar\\": (params: {str: (string | number), s?: (number), d?: (\\"retro\\" | \\"identicon\\")}) => { - params.s = params.s ?? 75; - params.d = params.d ?? \\"identicon\\"; - return \`\${base}https:/www.gravatar.com/avatar/\${params.str}\${appendSp({ s: params.s, d: params.d })}\` - } - } - - const appendSp = (sp?: Record, prefix: '?' | '&' = '?') => { - if (sp === undefined) return '' - const mapping = Object.entries(sp) - .filter(c => c[1] !== undefined) - .map(c => [c[0], String(c[1])]) - - const formated = new URLSearchParams(mapping).toString() - if (formated) { - return \`\${prefix}\${formated}\` - } - return '' - } - - - /** - * Add this type as a generic of the vite plugin \`kitRoutes\`. - * - * Full example: - * \`\`\`ts - * import type { KIT_ROUTES } from '$lib/ROUTES' - * import { kitRoutes } from 'vite-plugin-kit-routes' - * - * kitRoutes({ - * PAGES: { - * // here, \\"paths\\" it will be typed! - * } - * }) - * \`\`\` - */ - export type KIT_ROUTES = { - PAGES: { '_ROOT': never, 'subGroup': never, 'subGroup2': never, 'lang_contract': 'lang', 'lang_contract_id': 'lang' | 'id', 'lang_gp_one': 'lang', 'lang_gp_two': 'lang', 'lang_main': 'lang', 'lang_match_id_int': 'lang' | 'id', 'lang_site': 'lang', 'lang_site_id': 'lang' | 'id', 'lang_site_contract_siteId_contractId': 'lang' | 'siteId' | 'contractId', 'a_rest_z': 'rest', 'lay_normal': never, 'lay_root_layout': never, 'lay_skip': never } - SERVERS: { 'lang_contract': 'lang', 'lang_site': 'lang', 'api_graphql': never } - ACTIONS: { 'lang_contract_id': 'lang' | 'id', 'lang_site': 'lang', 'lang_site_contract': 'lang', 'lang_site_contract_siteId_contractId': 'lang' | 'siteId' | 'contractId' } - LINKS: { 'twitter': never, 'twitter_post': 'name' | 'id', 'gravatar': 'str' } - Params: { first: never, lang: never, id: never, limit: never, demo: never, siteId: never, contractId: never, rest: never, extra: never, name: never, str: never, s: never, d: never } - } - " - `) + expect(read(generated_file_path)?.includes("import { base } from '$app/paths'")).toBe(true) + expect(read(generated_file_path)?.includes('${base}')).toBe(true) }) }) diff --git a/packages/vite-plugin-kit-routes/src/routes/+layout.svelte b/packages/vite-plugin-kit-routes/src/routes/+layout.svelte index b6e8a7e48..d95a9d0ea 100644 --- a/packages/vite-plugin-kit-routes/src/routes/+layout.svelte +++ b/packages/vite-plugin-kit-routes/src/routes/+layout.svelte @@ -1,5 +1,4 @@ @@ -28,12 +35,12 @@
  • Home
  • -
  • Sites
  • - Sites (with Search Param) + Sites | + Sites (with Search Param)
  • - Site Paris + Site Paris
  • @@ -45,7 +52,7 @@ |
  • - match int 1 + match int 1
  • - match int a (expect 404) + match int a (expect 404)
  • - gp One + gp One
  • - gp Two + gp Two
  • Rest SWAGER GRAPHIQL diff --git a/packages/vite-plugin-kit-routes/src/routes/+page.svelte b/packages/vite-plugin-kit-routes/src/routes/+page.svelte index 22b9b04f9..b53366e1c 100644 --- a/packages/vite-plugin-kit-routes/src/routes/+page.svelte +++ b/packages/vite-plugin-kit-routes/src/routes/+page.svelte @@ -7,7 +7,7 @@ { lang: 'hu', caption: 'Magyar' }, { lang: 'at', caption: 'Deutsch' }, ].map(c => { - return { href: PAGES.lang_main({ lang: c.lang }), ...c } + return { href: PAGES.main({ lang: c.lang }), ...c } }) diff --git a/packages/vite-plugin-kit-routes/src/routes/[[lang]]/contract/[id]/+page.svelte b/packages/vite-plugin-kit-routes/src/routes/[[lang]]/contract/[id]/+page.svelte index d80e7b2f3..860c3004e 100644 --- a/packages/vite-plugin-kit-routes/src/routes/[[lang]]/contract/[id]/+page.svelte +++ b/packages/vite-plugin-kit-routes/src/routes/[[lang]]/contract/[id]/+page.svelte @@ -7,7 +7,7 @@ const id = $page.params.id - const action = ACTIONS.lang_contract_id({ + const action = ACTIONS.default_contract_id({ lang: $page.params.lang, id, }) diff --git a/packages/vite-plugin-kit-routes/src/routes/[[lang]]/site/+page.server.ts b/packages/vite-plugin-kit-routes/src/routes/[[lang]]/site/+page.server.ts index ce64d4fa5..6b7506eeb 100644 --- a/packages/vite-plugin-kit-routes/src/routes/[[lang]]/site/+page.server.ts +++ b/packages/vite-plugin-kit-routes/src/routes/[[lang]]/site/+page.server.ts @@ -1,6 +1,5 @@ import type { Actions } from './$types.d.ts' export const actions = { - action1: async () => {}, - action2: async () => {}, + create: async () => {}, } satisfies Actions diff --git a/packages/vite-plugin-kit-routes/src/routes/[[lang]]/site/[id]/+page.server.ts b/packages/vite-plugin-kit-routes/src/routes/[[lang]]/site/[id]/+page.server.ts new file mode 100644 index 000000000..ea3f0a886 --- /dev/null +++ b/packages/vite-plugin-kit-routes/src/routes/[[lang]]/site/[id]/+page.server.ts @@ -0,0 +1,6 @@ +import type { Actions } from '../$types.js' + +export const actions = { + update: async () => {}, + delete: async () => {}, +} satisfies Actions diff --git a/packages/vite-plugin-kit-routes/src/routes/[[lang]]/site_contract/+page.server.ts b/packages/vite-plugin-kit-routes/src/routes/[[lang]]/site_contract/+page.server.ts index fb3630d55..71f1f3171 100644 --- a/packages/vite-plugin-kit-routes/src/routes/[[lang]]/site_contract/+page.server.ts +++ b/packages/vite-plugin-kit-routes/src/routes/[[lang]]/site_contract/+page.server.ts @@ -1,10 +1,10 @@ export const actions = { noSatisfies: async data => { - console.log(`sendSomething`) + console.log(`noSatisfies`) return { body: { - message: `Yes, you sent me something (${data.params.lang}! ✨ Thank you!`, + message: `Yes, you sent me (${data.params.lang}! ✨ Thank you!`, }, } }, diff --git a/packages/vite-plugin-kit-routes/src/routes/[[lang]]/site_contract/[siteId]-[contractId]/+page.server.ts b/packages/vite-plugin-kit-routes/src/routes/[[lang]]/site_contract/[siteId]-[contractId]/+page.server.ts index 8f808f4af..56b01def7 100644 --- a/packages/vite-plugin-kit-routes/src/routes/[[lang]]/site_contract/[siteId]-[contractId]/+page.server.ts +++ b/packages/vite-plugin-kit-routes/src/routes/[[lang]]/site_contract/[siteId]-[contractId]/+page.server.ts @@ -1,12 +1,12 @@ import type { Actions } from './$types.d.ts' export const actions = { - sendSomething: async data => { - console.log(`sendSomething`, data.params, data.url.searchParams.get('extra')) + send: async data => { + console.log(`send`, data.params, data.url.searchParams.get('extra')) return { body: { - message: `Yes, you sent me something (${data.params.siteId}, ${ + message: `Yes, you sent (${data.params.siteId}, ${ data.params.contractId }, ${data.url.searchParams.get('extra')})! ✨ Thank you!`, }, diff --git a/packages/vite-plugin-kit-routes/src/routes/[[lang]]/site_contract/[siteId]-[contractId]/+page.svelte b/packages/vite-plugin-kit-routes/src/routes/[[lang]]/site_contract/[siteId]-[contractId]/+page.svelte index ccc026887..fe9ca7516 100644 --- a/packages/vite-plugin-kit-routes/src/routes/[[lang]]/site_contract/[siteId]-[contractId]/+page.svelte +++ b/packages/vite-plugin-kit-routes/src/routes/[[lang]]/site_contract/[siteId]-[contractId]/+page.svelte @@ -9,10 +9,10 @@ const contractId = $page.params.contractId // 🤞 before, hardcoded string, error prone - // const action = `/en/site_contract/${siteId}-${contractId}?/sendSomething` + // const action = `/en/site_contract/${siteId}-${contractId}?/send` // ✅ after, typechecked route, no more errors - const action = ACTIONS.lang_site_contract_siteId_contractId('sendSomething', { + const action = ACTIONS.send_site_contract_siteId_contractId({ lang: $page.params.lang, siteId, contractId, diff --git a/packages/vite-plugin-kit-routes/src/test/.gitignore b/packages/vite-plugin-kit-routes/src/test/.gitignore deleted file mode 100644 index 383202dc7..000000000 --- a/packages/vite-plugin-kit-routes/src/test/.gitignore +++ /dev/null @@ -1 +0,0 @@ -ROUTES_* diff --git a/packages/vite-plugin-kit-routes/src/test/ROUTES_base.ts b/packages/vite-plugin-kit-routes/src/test/ROUTES_base.ts new file mode 100644 index 000000000..575b85dec --- /dev/null +++ b/packages/vite-plugin-kit-routes/src/test/ROUTES_base.ts @@ -0,0 +1,162 @@ +/** + * This file was generated by 'vite-plugin-kit-routes' + * + * >> DO NOT EDIT THIS FILE MANUALLY << + */ +import { base } from '$app/paths' + +/** + * PAGES + */ +const PAGES = { + "/": `${base}/`, + "/subGroup": `${base}/subGroup`, + "/subGroup2": `${base}/subGroup2`, + "/contract": (params?: { lang?: (string | number) }) => { + return `${base}${params?.lang ? `/${params?.lang}`: ''}/contract` + }, + "/contract/[id]": (params: { id: (string | number), lang?: (string | number) }) => { + return `${base}${params?.lang ? `/${params?.lang}`: ''}/contract/${params.id}` + }, + "/gp/one": (params?: { lang?: (string | number) }) => { + return `${base}${params?.lang ? `/${params?.lang}`: ''}/gp/one` + }, + "/gp/two": (params?: { lang?: (string | number) }) => { + return `${base}${params?.lang ? `/${params?.lang}`: ''}/gp/two` + }, + "/main": (params?: { lang?: (string | number) }) => { + return `${base}${params?.lang ? `/${params?.lang}`: ''}/main` + }, + "/match/[id=int]": (params: { id: (string | number), lang?: (string | number) }) => { + return `${base}${params?.lang ? `/${params?.lang}`: ''}/match/${params.id}` + }, + "/site": (params?: { lang?: (string | number) }) => { + return `${base}${params?.lang ? `/${params?.lang}`: ''}/site` + }, + "/site/[id]": (params: { id: (string | number), lang?: (string | number) }) => { + return `${base}${params?.lang ? `/${params?.lang}`: ''}/site/${params.id}` + }, + "/site_contract/[siteId]-[contractId]": (params: { siteId: (string | number), contractId: (string | number), lang?: (string | number) }) => { + return `${base}${params?.lang ? `/${params?.lang}`: ''}/site_contract/${params.siteId}-${params.contractId}` + }, + "/a/[...rest]/z": (params: { rest: (string | number)[] }) => { + return `${base}/a/${params.rest?.join('/')}/z` + }, + "/lay/normal": `${base}/lay/normal`, + "/lay/root-layout": `${base}/lay/root-layout`, + "/lay/skip": `${base}/lay/skip` +} + +/** + * SERVERS + */ +const SERVERS = { + "GET /contract": (params?: { lang?: (string | number) }) => { + return `${base}${params?.lang ? `/${params?.lang}`: ''}/contract` + }, + "POST /contract": (params?: { lang?: (string | number) }) => { + return `${base}${params?.lang ? `/${params?.lang}`: ''}/contract` + }, + "GET /site": (params?: { lang?: (string | number) }) => { + return `${base}${params?.lang ? `/${params?.lang}`: ''}/site` + }, + "GET /api/graphql": `${base}/api/graphql`, + "POST /api/graphql": `${base}/api/graphql` +} + +/** + * ACTIONS + */ +const ACTIONS = { + "default /contract/[id]": (params: { id: (string | number), lang?: (string | number) }) => { + return `${base}${params?.lang ? `/${params?.lang}`: ''}/contract/${params.id}` + }, + "create /site": (params?: { lang?: (string | number) }) => { + return `${base}${params?.lang ? `/${params?.lang}`: ''}/site?/create` + }, + "update /site/[id]": (params: { id: (string | number), lang?: (string | number) }) => { + return `${base}${params?.lang ? `/${params?.lang}`: ''}/site/${params.id}?/update` + }, + "delete /site/[id]": (params: { id: (string | number), lang?: (string | number) }) => { + return `${base}${params?.lang ? `/${params?.lang}`: ''}/site/${params.id}?/delete` + }, + "noSatisfies /site_contract": (params?: { lang?: (string | number) }) => { + return `${base}${params?.lang ? `/${params?.lang}`: ''}/site_contract?/noSatisfies` + }, + "send /site_contract/[siteId]-[contractId]": (params: { siteId: (string | number), contractId: (string | number), lang?: (string | number) }) => { + return `${base}${params?.lang ? `/${params?.lang}`: ''}/site_contract/${params.siteId}-${params.contractId}?/send` + } +} + +/** + * LINKS + */ +const LINKS = { + +} + +/** + * Append search params to a string + */ +const appendSp = (sp?: Record, prefix: '?' | '&' = '?') => { + if (sp === undefined) return '' + const mapping = Object.entries(sp) + .filter(c => c[1] !== undefined) + .map(c => [c[0], String(c[1])]) + + const formated = new URLSearchParams(mapping).toString() + if (formated) { + return `${prefix}${formated}` + } + return '' +} + +// route function helpers +type NonFunctionKeys = { [K in keyof T]: T[K] extends Function ? never : K }[keyof T] +type FunctionKeys = { [K in keyof T]: T[K] extends Function ? K : never }[keyof T] +type FunctionParams = T extends (...args: infer P) => any ? P : never + +const AllObjs = { ...PAGES, ...ACTIONS, ...SERVERS, ...LINKS } +type AllTypes = typeof AllObjs + +/** + * To be used like this: + * ```ts + * import { route } from '$lib/ROUTES' + * + * route('site_id', { id: 1 }) + * ``` + */ +export function route>(key: T, ...params: FunctionParams): string +export function route>(key: T): string +export function route(key: T, ...params: any[]): string { + if (AllObjs[key] instanceof Function) { + const element = (AllObjs as any)[key] as (...args: any[]) => string + return element(...params) + } else { + return AllObjs[key] as string + } +} + +/** +* Add this type as a generic of the vite plugin `kitRoutes`. +* +* Full example: +* ```ts +* import type { KIT_ROUTES } from '$lib/ROUTES' +* import { kitRoutes } from 'vite-plugin-kit-routes' +* +* kitRoutes({ +* PAGES: { +* // here, key of object will be typed! +* } +* }) +* ``` +*/ +export type KIT_ROUTES = { + PAGES: { '/': never, '/subGroup': never, '/subGroup2': never, '/contract': 'lang', '/contract/[id]': 'id' | 'lang', '/gp/one': 'lang', '/gp/two': 'lang', '/main': 'lang', '/match/[id=int]': 'id' | 'lang', '/site': 'lang', '/site/[id]': 'id' | 'lang', '/site_contract/[siteId]-[contractId]': 'siteId' | 'contractId' | 'lang', '/a/[...rest]/z': 'rest', '/lay/normal': never, '/lay/root-layout': never, '/lay/skip': never } + SERVERS: { 'GET /contract': 'lang', 'POST /contract': 'lang', 'GET /site': 'lang', 'GET /api/graphql': never, 'POST /api/graphql': never } + ACTIONS: { 'default /contract/[id]': 'id' | 'lang', 'create /site': 'lang', 'update /site/[id]': 'id' | 'lang', 'delete /site/[id]': 'id' | 'lang', 'noSatisfies /site_contract': 'lang', 'send /site_contract/[siteId]-[contractId]': 'siteId' | 'contractId' | 'lang' } + LINKS: Record + Params: { lang: never, id: never, siteId: never, contractId: never, rest: never } +} diff --git a/packages/vite-plugin-kit-routes/src/test/ROUTES_format-object-path.ts b/packages/vite-plugin-kit-routes/src/test/ROUTES_format-object-path.ts new file mode 100644 index 000000000..8e4745dc8 --- /dev/null +++ b/packages/vite-plugin-kit-routes/src/test/ROUTES_format-object-path.ts @@ -0,0 +1,148 @@ +/** + * This file was generated by 'vite-plugin-kit-routes' + * + * >> DO NOT EDIT THIS FILE MANUALLY << + */ + +/** + * PAGES + */ +export const PAGES = { + "/": `/`, + "/subGroup": `/subGroup`, + "/subGroup2": (params: { first: (string | number) }) => { + return `/subGroup2${appendSp({ first: params?.first })}` + }, + "/contract": (params?: { lang?: ('fr' | 'en' | 'hu' | 'at' | string) }, sp?: Record) => { + return `${params?.lang ? `/${params?.lang}`: ''}/contract${appendSp(sp)}` + }, + "/contract/[id]": (params: { id: (string | number), lang?: ('fr' | 'en' | 'hu' | 'at' | string) }) => { + return `${params?.lang ? `/${params?.lang}`: ''}/contract/${params.id}` + }, + "/gp/one": (params?: { lang?: ('fr' | 'en' | 'hu' | 'at' | string) }) => { + return `${params?.lang ? `/${params?.lang}`: ''}/gp/one` + }, + "/gp/two": (params?: { lang?: ('fr' | 'en' | 'hu' | 'at' | string) }) => { + return `${params?.lang ? `/${params?.lang}`: ''}/gp/two` + }, + "/main": (params?: { lang?: ('fr' | 'en' | 'hu' | 'at' | string) }) => { + return `${params?.lang ? `/${params?.lang}`: ''}/main` + }, + "/match/[id=int]": (params: { id: (string | number), lang?: ('fr' | 'en' | 'hu' | 'at' | string) }) => { + return `${params?.lang ? `/${params?.lang}`: ''}/match/${params.id}` + }, + "/site": (params?: { lang?: ('fr' | 'en' | 'hu' | 'at' | string), limit?: (number) }, sp?: Record) => { + return `${params?.lang ? `/${params?.lang}`: ''}/site${appendSp({ limit: params?.limit, ...sp })}` + }, + "/site/[id]": (params?: { lang?: ('fr' | 'hu' | undefined), id?: (string), limit?: (number), demo?: (string) }) => { + params = params ?? {} + params.lang = params.lang ?? "fr"; + params.id = params.id ?? "Vienna"; + return `${params?.lang ? `/${params?.lang}`: ''}/site/${params.id}${appendSp({ limit: params?.limit, demo: params?.demo })}` + }, + "/site_contract/[siteId]-[contractId]": (params: { siteId: (string | number), contractId: (string | number), lang?: ('fr' | 'en' | 'hu' | 'at' | string), limit?: (number) }) => { + return `${params?.lang ? `/${params?.lang}`: ''}/site_contract/${params.siteId}-${params.contractId}${appendSp({ limit: params?.limit })}` + }, + "/a/[...rest]/z": (params: { rest: (string | number)[] }) => { + return `/a/${params.rest?.join('/')}/z` + }, + "/lay/normal": `/lay/normal`, + "/lay/root-layout": `/lay/root-layout`, + "/lay/skip": `/lay/skip` +} + +/** + * SERVERS + */ +export const SERVERS = { + "GET /contract": (params?: { lang?: ('fr' | 'en' | 'hu' | 'at' | string) }) => { + return `${params?.lang ? `/${params?.lang}`: ''}/contract` + }, + "POST /contract": (params?: { lang?: ('fr' | 'en' | 'hu' | 'at' | string) }) => { + return `${params?.lang ? `/${params?.lang}`: ''}/contract` + }, + "GET /site": (params?: { lang?: ('fr' | 'en' | 'hu' | 'at' | string) }) => { + return `${params?.lang ? `/${params?.lang}`: ''}/site` + }, + "GET /api/graphql": `/api/graphql`, + "POST /api/graphql": `/api/graphql` +} + +/** + * ACTIONS + */ +export const ACTIONS = { + "default /contract/[id]": (params: { id: (string | number), lang?: ('fr' | 'en' | 'hu' | 'at' | string), limit?: (number) }) => { + return `${params?.lang ? `/${params?.lang}`: ''}/contract/${params.id}${appendSp({ limit: params?.limit })}` + }, + "create /site": (params?: { lang?: ('fr' | 'en' | 'hu' | 'at' | string) }) => { + return `${params?.lang ? `/${params?.lang}`: ''}/site?/create` + }, + "update /site/[id]": (params: { id: (string | number), lang?: ('fr' | 'en' | 'hu' | 'at' | string) }) => { + return `${params?.lang ? `/${params?.lang}`: ''}/site/${params.id}?/update` + }, + "delete /site/[id]": (params: { id: (string | number), lang?: ('fr' | 'en' | 'hu' | 'at' | string) }) => { + return `${params?.lang ? `/${params?.lang}`: ''}/site/${params.id}?/delete` + }, + "noSatisfies /site_contract": (params?: { lang?: ('fr' | 'en' | 'hu' | 'at' | string) }) => { + return `${params?.lang ? `/${params?.lang}`: ''}/site_contract?/noSatisfies` + }, + "send /site_contract/[siteId]-[contractId]": (params: { siteId: (string | number), contractId: (string | number), lang?: ('fr' | 'en' | 'hu' | 'at' | string), extra?: ('A' | 'B') }) => { + params.extra = params.extra ?? "A"; + return `${params?.lang ? `/${params?.lang}`: ''}/site_contract/${params.siteId}-${params.contractId}?/send${appendSp({ extra: params?.extra }, '&')}` + } +} + +/** + * LINKS + */ +export const LINKS = { + "twitter": `https:/twitter.com/jycouet`, + "twitter_post": (params: { name: (string | number), id: (string | number) }) => { + return `https:/twitter.com/${params.name}/status/${params.id}` + }, + "gravatar": (params: { str: (string | number), s?: (number), d?: ("retro" | "identicon") }) => { + params.s = params.s ?? 75; + params.d = params.d ?? "identicon"; + return `https:/www.gravatar.com/avatar/${params.str}${appendSp({ s: params?.s, d: params?.d })}` + } +} + +/** + * Append search params to a string + */ +const appendSp = (sp?: Record, prefix: '?' | '&' = '?') => { + if (sp === undefined) return '' + const mapping = Object.entries(sp) + .filter(c => c[1] !== undefined) + .map(c => [c[0], String(c[1])]) + + const formated = new URLSearchParams(mapping).toString() + if (formated) { + return `${prefix}${formated}` + } + return '' +} + +/** +* Add this type as a generic of the vite plugin `kitRoutes`. +* +* Full example: +* ```ts +* import type { KIT_ROUTES } from '$lib/ROUTES' +* import { kitRoutes } from 'vite-plugin-kit-routes' +* +* kitRoutes({ +* PAGES: { +* // here, key of object will be typed! +* } +* }) +* ``` +*/ +export type KIT_ROUTES = { + PAGES: { '/': never, '/subGroup': never, '/subGroup2': never, '/contract': 'lang', '/contract/[id]': 'id' | 'lang', '/gp/one': 'lang', '/gp/two': 'lang', '/main': 'lang', '/match/[id=int]': 'id' | 'lang', '/site': 'lang', '/site/[id]': 'lang' | 'id', '/site_contract/[siteId]-[contractId]': 'siteId' | 'contractId' | 'lang', '/a/[...rest]/z': 'rest', '/lay/normal': never, '/lay/root-layout': never, '/lay/skip': never } + SERVERS: { 'GET /contract': 'lang', 'POST /contract': 'lang', 'GET /site': 'lang', 'GET /api/graphql': never, 'POST /api/graphql': never } + ACTIONS: { 'default /contract/[id]': 'id' | 'lang', 'create /site': 'lang', 'update /site/[id]': 'id' | 'lang', 'delete /site/[id]': 'id' | 'lang', 'noSatisfies /site_contract': 'lang', 'send /site_contract/[siteId]-[contractId]': 'siteId' | 'contractId' | 'lang' } + LINKS: { 'twitter': never, 'twitter_post': 'name' | 'id', 'gravatar': 'str' } + Params: { first: never, lang: never, id: never, limit: never, demo: never, siteId: never, contractId: never, rest: never, extra: never, name: never, str: never, s: never, d: never } +} diff --git a/packages/vite-plugin-kit-routes/src/test/ROUTES_format-object-symbol.ts b/packages/vite-plugin-kit-routes/src/test/ROUTES_format-object-symbol.ts new file mode 100644 index 000000000..deb019ccf --- /dev/null +++ b/packages/vite-plugin-kit-routes/src/test/ROUTES_format-object-symbol.ts @@ -0,0 +1,148 @@ +/** + * This file was generated by 'vite-plugin-kit-routes' + * + * >> DO NOT EDIT THIS FILE MANUALLY << + */ + +/** + * PAGES + */ +export const PAGES = { + "_ROOT": `/`, + "subGroup": `/subGroup`, + "subGroup2": (params: { first: (string | number) }) => { + return `/subGroup2${appendSp({ first: params?.first })}` + }, + "contract": (params?: { lang?: ('fr' | 'en' | 'hu' | 'at' | string) }, sp?: Record) => { + return `${params?.lang ? `/${params?.lang}`: ''}/contract${appendSp(sp)}` + }, + "contract_id": (params: { id: (string | number), lang?: ('fr' | 'en' | 'hu' | 'at' | string) }) => { + return `${params?.lang ? `/${params?.lang}`: ''}/contract/${params.id}` + }, + "gp_one": (params?: { lang?: ('fr' | 'en' | 'hu' | 'at' | string) }) => { + return `${params?.lang ? `/${params?.lang}`: ''}/gp/one` + }, + "gp_two": (params?: { lang?: ('fr' | 'en' | 'hu' | 'at' | string) }) => { + return `${params?.lang ? `/${params?.lang}`: ''}/gp/two` + }, + "main": (params?: { lang?: ('fr' | 'en' | 'hu' | 'at' | string) }) => { + return `${params?.lang ? `/${params?.lang}`: ''}/main` + }, + "match_id_int": (params: { id: (string | number), lang?: ('fr' | 'en' | 'hu' | 'at' | string) }) => { + return `${params?.lang ? `/${params?.lang}`: ''}/match/${params.id}` + }, + "site": (params?: { lang?: ('fr' | 'en' | 'hu' | 'at' | string), limit?: (number) }, sp?: Record) => { + return `${params?.lang ? `/${params?.lang}`: ''}/site${appendSp({ limit: params?.limit, ...sp })}` + }, + "site_id": (params?: { lang?: ('fr' | 'hu' | undefined), id?: (string), limit?: (number), demo?: (string) }) => { + params = params ?? {} + params.lang = params.lang ?? "fr"; + params.id = params.id ?? "Vienna"; + return `${params?.lang ? `/${params?.lang}`: ''}/site/${params.id}${appendSp({ limit: params?.limit, demo: params?.demo })}` + }, + "site_contract_siteId_contractId": (params: { siteId: (string | number), contractId: (string | number), lang?: ('fr' | 'en' | 'hu' | 'at' | string), limit?: (number) }) => { + return `${params?.lang ? `/${params?.lang}`: ''}/site_contract/${params.siteId}-${params.contractId}${appendSp({ limit: params?.limit })}` + }, + "a_rest_z": (params: { rest: (string | number)[] }) => { + return `/a/${params.rest?.join('/')}/z` + }, + "lay_normal": `/lay/normal`, + "lay_root_layout": `/lay/root-layout`, + "lay_skip": `/lay/skip` +} + +/** + * SERVERS + */ +export const SERVERS = { + "GET_contract": (params?: { lang?: ('fr' | 'en' | 'hu' | 'at' | string) }) => { + return `${params?.lang ? `/${params?.lang}`: ''}/contract` + }, + "POST_contract": (params?: { lang?: ('fr' | 'en' | 'hu' | 'at' | string) }) => { + return `${params?.lang ? `/${params?.lang}`: ''}/contract` + }, + "GET_site": (params?: { lang?: ('fr' | 'en' | 'hu' | 'at' | string) }) => { + return `${params?.lang ? `/${params?.lang}`: ''}/site` + }, + "GET_api_graphql": `/api/graphql`, + "POST_api_graphql": `/api/graphql` +} + +/** + * ACTIONS + */ +export const ACTIONS = { + "default_contract_id": (params: { id: (string | number), lang?: ('fr' | 'en' | 'hu' | 'at' | string), limit?: (number) }) => { + return `${params?.lang ? `/${params?.lang}`: ''}/contract/${params.id}${appendSp({ limit: params?.limit })}` + }, + "create_site": (params?: { lang?: ('fr' | 'en' | 'hu' | 'at' | string) }) => { + return `${params?.lang ? `/${params?.lang}`: ''}/site?/create` + }, + "update_site_id": (params: { id: (string | number), lang?: ('fr' | 'en' | 'hu' | 'at' | string) }) => { + return `${params?.lang ? `/${params?.lang}`: ''}/site/${params.id}?/update` + }, + "delete_site_id": (params: { id: (string | number), lang?: ('fr' | 'en' | 'hu' | 'at' | string) }) => { + return `${params?.lang ? `/${params?.lang}`: ''}/site/${params.id}?/delete` + }, + "noSatisfies_site_contract": (params?: { lang?: ('fr' | 'en' | 'hu' | 'at' | string) }) => { + return `${params?.lang ? `/${params?.lang}`: ''}/site_contract?/noSatisfies` + }, + "send_site_contract_siteId_contractId": (params: { siteId: (string | number), contractId: (string | number), lang?: ('fr' | 'en' | 'hu' | 'at' | string), extra?: ('A' | 'B') }) => { + params.extra = params.extra ?? "A"; + return `${params?.lang ? `/${params?.lang}`: ''}/site_contract/${params.siteId}-${params.contractId}?/send${appendSp({ extra: params?.extra }, '&')}` + } +} + +/** + * LINKS + */ +export const LINKS = { + "twitter": `https:/twitter.com/jycouet`, + "twitter_post": (params: { name: (string | number), id: (string | number) }) => { + return `https:/twitter.com/${params.name}/status/${params.id}` + }, + "gravatar": (params: { str: (string | number), s?: (number), d?: ("retro" | "identicon") }) => { + params.s = params.s ?? 75; + params.d = params.d ?? "identicon"; + return `https:/www.gravatar.com/avatar/${params.str}${appendSp({ s: params?.s, d: params?.d })}` + } +} + +/** + * Append search params to a string + */ +const appendSp = (sp?: Record, prefix: '?' | '&' = '?') => { + if (sp === undefined) return '' + const mapping = Object.entries(sp) + .filter(c => c[1] !== undefined) + .map(c => [c[0], String(c[1])]) + + const formated = new URLSearchParams(mapping).toString() + if (formated) { + return `${prefix}${formated}` + } + return '' +} + +/** +* Add this type as a generic of the vite plugin `kitRoutes`. +* +* Full example: +* ```ts +* import type { KIT_ROUTES } from '$lib/ROUTES' +* import { kitRoutes } from 'vite-plugin-kit-routes' +* +* kitRoutes({ +* PAGES: { +* // here, key of object will be typed! +* } +* }) +* ``` +*/ +export type KIT_ROUTES = { + PAGES: { '_ROOT': never, 'subGroup': never, 'subGroup2': never, 'contract': 'lang', 'contract_id': 'id' | 'lang', 'gp_one': 'lang', 'gp_two': 'lang', 'main': 'lang', 'match_id_int': 'id' | 'lang', 'site': 'lang', 'site_id': 'lang' | 'id', 'site_contract_siteId_contractId': 'siteId' | 'contractId' | 'lang', 'a_rest_z': 'rest', 'lay_normal': never, 'lay_root_layout': never, 'lay_skip': never } + SERVERS: { 'GET_contract': 'lang', 'POST_contract': 'lang', 'GET_site': 'lang', 'GET_api_graphql': never, 'POST_api_graphql': never } + ACTIONS: { 'default_contract_id': 'id' | 'lang', 'create_site': 'lang', 'update_site_id': 'id' | 'lang', 'delete_site_id': 'id' | 'lang', 'noSatisfies_site_contract': 'lang', 'send_site_contract_siteId_contractId': 'siteId' | 'contractId' | 'lang' } + LINKS: { 'twitter': never, 'twitter_post': 'name' | 'id', 'gravatar': 'str' } + Params: { first: never, lang: never, id: never, limit: never, demo: never, siteId: never, contractId: never, rest: never, extra: never, name: never, str: never, s: never, d: never } +} diff --git a/packages/vite-plugin-kit-routes/src/test/ROUTES_format-route-path.ts b/packages/vite-plugin-kit-routes/src/test/ROUTES_format-route-path.ts new file mode 100644 index 000000000..42ba4c697 --- /dev/null +++ b/packages/vite-plugin-kit-routes/src/test/ROUTES_format-route-path.ts @@ -0,0 +1,175 @@ +/** + * This file was generated by 'vite-plugin-kit-routes' + * + * >> DO NOT EDIT THIS FILE MANUALLY << + */ + +/** + * PAGES + */ +const PAGES = { + "/": `/`, + "/subGroup": `/subGroup`, + "/subGroup2": (params: { first: (string | number) }) => { + return `/subGroup2${appendSp({ first: params?.first })}` + }, + "/contract": (params?: { lang?: ('fr' | 'en' | 'hu' | 'at' | string) }, sp?: Record) => { + return `${params?.lang ? `/${params?.lang}`: ''}/contract${appendSp(sp)}` + }, + "/contract/[id]": (params: { id: (string | number), lang?: ('fr' | 'en' | 'hu' | 'at' | string) }) => { + return `${params?.lang ? `/${params?.lang}`: ''}/contract/${params.id}` + }, + "/gp/one": (params?: { lang?: ('fr' | 'en' | 'hu' | 'at' | string) }) => { + return `${params?.lang ? `/${params?.lang}`: ''}/gp/one` + }, + "/gp/two": (params?: { lang?: ('fr' | 'en' | 'hu' | 'at' | string) }) => { + return `${params?.lang ? `/${params?.lang}`: ''}/gp/two` + }, + "/main": (params?: { lang?: ('fr' | 'en' | 'hu' | 'at' | string) }) => { + return `${params?.lang ? `/${params?.lang}`: ''}/main` + }, + "/match/[id=int]": (params: { id: (string | number), lang?: ('fr' | 'en' | 'hu' | 'at' | string) }) => { + return `${params?.lang ? `/${params?.lang}`: ''}/match/${params.id}` + }, + "/site": (params?: { lang?: ('fr' | 'en' | 'hu' | 'at' | string), limit?: (number) }, sp?: Record) => { + return `${params?.lang ? `/${params?.lang}`: ''}/site${appendSp({ limit: params?.limit, ...sp })}` + }, + "/site/[id]": (params?: { lang?: ('fr' | 'hu' | undefined), id?: (string), limit?: (number), demo?: (string) }) => { + params = params ?? {} + params.lang = params.lang ?? "fr"; + params.id = params.id ?? "Vienna"; + return `${params?.lang ? `/${params?.lang}`: ''}/site/${params.id}${appendSp({ limit: params?.limit, demo: params?.demo })}` + }, + "/site_contract/[siteId]-[contractId]": (params: { siteId: (string | number), contractId: (string | number), lang?: ('fr' | 'en' | 'hu' | 'at' | string), limit?: (number) }) => { + return `${params?.lang ? `/${params?.lang}`: ''}/site_contract/${params.siteId}-${params.contractId}${appendSp({ limit: params?.limit })}` + }, + "/a/[...rest]/z": (params: { rest: (string | number)[] }) => { + return `/a/${params.rest?.join('/')}/z` + }, + "/lay/normal": `/lay/normal`, + "/lay/root-layout": `/lay/root-layout`, + "/lay/skip": `/lay/skip` +} + +/** + * SERVERS + */ +const SERVERS = { + "GET /contract": (params?: { lang?: ('fr' | 'en' | 'hu' | 'at' | string) }) => { + return `${params?.lang ? `/${params?.lang}`: ''}/contract` + }, + "POST /contract": (params?: { lang?: ('fr' | 'en' | 'hu' | 'at' | string) }) => { + return `${params?.lang ? `/${params?.lang}`: ''}/contract` + }, + "GET /site": (params?: { lang?: ('fr' | 'en' | 'hu' | 'at' | string) }) => { + return `${params?.lang ? `/${params?.lang}`: ''}/site` + }, + "GET /api/graphql": `/api/graphql`, + "POST /api/graphql": `/api/graphql` +} + +/** + * ACTIONS + */ +const ACTIONS = { + "default /contract/[id]": (params: { id: (string | number), lang?: ('fr' | 'en' | 'hu' | 'at' | string), limit?: (number) }) => { + return `${params?.lang ? `/${params?.lang}`: ''}/contract/${params.id}${appendSp({ limit: params?.limit })}` + }, + "create /site": (params?: { lang?: ('fr' | 'en' | 'hu' | 'at' | string) }) => { + return `${params?.lang ? `/${params?.lang}`: ''}/site?/create` + }, + "update /site/[id]": (params: { id: (string | number), lang?: ('fr' | 'en' | 'hu' | 'at' | string) }) => { + return `${params?.lang ? `/${params?.lang}`: ''}/site/${params.id}?/update` + }, + "delete /site/[id]": (params: { id: (string | number), lang?: ('fr' | 'en' | 'hu' | 'at' | string) }) => { + return `${params?.lang ? `/${params?.lang}`: ''}/site/${params.id}?/delete` + }, + "noSatisfies /site_contract": (params?: { lang?: ('fr' | 'en' | 'hu' | 'at' | string) }) => { + return `${params?.lang ? `/${params?.lang}`: ''}/site_contract?/noSatisfies` + }, + "send /site_contract/[siteId]-[contractId]": (params: { siteId: (string | number), contractId: (string | number), lang?: ('fr' | 'en' | 'hu' | 'at' | string), extra?: ('A' | 'B') }) => { + params.extra = params.extra ?? "A"; + return `${params?.lang ? `/${params?.lang}`: ''}/site_contract/${params.siteId}-${params.contractId}?/send${appendSp({ extra: params?.extra }, '&')}` + } +} + +/** + * LINKS + */ +const LINKS = { + "twitter": `https:/twitter.com/jycouet`, + "twitter_post": (params: { name: (string | number), id: (string | number) }) => { + return `https:/twitter.com/${params.name}/status/${params.id}` + }, + "gravatar": (params: { str: (string | number), s?: (number), d?: ("retro" | "identicon") }) => { + params.s = params.s ?? 75; + params.d = params.d ?? "identicon"; + return `https:/www.gravatar.com/avatar/${params.str}${appendSp({ s: params?.s, d: params?.d })}` + } +} + +/** + * Append search params to a string + */ +const appendSp = (sp?: Record, prefix: '?' | '&' = '?') => { + if (sp === undefined) return '' + const mapping = Object.entries(sp) + .filter(c => c[1] !== undefined) + .map(c => [c[0], String(c[1])]) + + const formated = new URLSearchParams(mapping).toString() + if (formated) { + return `${prefix}${formated}` + } + return '' +} + +// route function helpers +type NonFunctionKeys = { [K in keyof T]: T[K] extends Function ? never : K }[keyof T] +type FunctionKeys = { [K in keyof T]: T[K] extends Function ? K : never }[keyof T] +type FunctionParams = T extends (...args: infer P) => any ? P : never + +const AllObjs = { ...PAGES, ...ACTIONS, ...SERVERS, ...LINKS } +type AllTypes = typeof AllObjs + +/** + * To be used like this: + * ```ts + * import { route } from '$lib/ROUTES' + * + * route('site_id', { id: 1 }) + * ``` + */ +export function route>(key: T, ...params: FunctionParams): string +export function route>(key: T): string +export function route(key: T, ...params: any[]): string { + if (AllObjs[key] instanceof Function) { + const element = (AllObjs as any)[key] as (...args: any[]) => string + return element(...params) + } else { + return AllObjs[key] as string + } +} + +/** +* Add this type as a generic of the vite plugin `kitRoutes`. +* +* Full example: +* ```ts +* import type { KIT_ROUTES } from '$lib/ROUTES' +* import { kitRoutes } from 'vite-plugin-kit-routes' +* +* kitRoutes({ +* PAGES: { +* // here, key of object will be typed! +* } +* }) +* ``` +*/ +export type KIT_ROUTES = { + PAGES: { '/': never, '/subGroup': never, '/subGroup2': never, '/contract': 'lang', '/contract/[id]': 'id' | 'lang', '/gp/one': 'lang', '/gp/two': 'lang', '/main': 'lang', '/match/[id=int]': 'id' | 'lang', '/site': 'lang', '/site/[id]': 'lang' | 'id', '/site_contract/[siteId]-[contractId]': 'siteId' | 'contractId' | 'lang', '/a/[...rest]/z': 'rest', '/lay/normal': never, '/lay/root-layout': never, '/lay/skip': never } + SERVERS: { 'GET /contract': 'lang', 'POST /contract': 'lang', 'GET /site': 'lang', 'GET /api/graphql': never, 'POST /api/graphql': never } + ACTIONS: { 'default /contract/[id]': 'id' | 'lang', 'create /site': 'lang', 'update /site/[id]': 'id' | 'lang', 'delete /site/[id]': 'id' | 'lang', 'noSatisfies /site_contract': 'lang', 'send /site_contract/[siteId]-[contractId]': 'siteId' | 'contractId' | 'lang' } + LINKS: { 'twitter': never, 'twitter_post': 'name' | 'id', 'gravatar': 'str' } + Params: { first: never, lang: never, id: never, limit: never, demo: never, siteId: never, contractId: never, rest: never, extra: never, name: never, str: never, s: never, d: never } +} diff --git a/packages/vite-plugin-kit-routes/src/test/ROUTES_format-route-symbol.ts b/packages/vite-plugin-kit-routes/src/test/ROUTES_format-route-symbol.ts new file mode 100644 index 000000000..092f5cc4b --- /dev/null +++ b/packages/vite-plugin-kit-routes/src/test/ROUTES_format-route-symbol.ts @@ -0,0 +1,175 @@ +/** + * This file was generated by 'vite-plugin-kit-routes' + * + * >> DO NOT EDIT THIS FILE MANUALLY << + */ + +/** + * PAGES + */ +const PAGES = { + "_ROOT": `/`, + "subGroup": `/subGroup`, + "subGroup2": (params: { first: (string | number) }) => { + return `/subGroup2${appendSp({ first: params?.first })}` + }, + "contract": (params?: { lang?: ('fr' | 'en' | 'hu' | 'at' | string) }, sp?: Record) => { + return `${params?.lang ? `/${params?.lang}`: ''}/contract${appendSp(sp)}` + }, + "contract_id": (params: { id: (string | number), lang?: ('fr' | 'en' | 'hu' | 'at' | string) }) => { + return `${params?.lang ? `/${params?.lang}`: ''}/contract/${params.id}` + }, + "gp_one": (params?: { lang?: ('fr' | 'en' | 'hu' | 'at' | string) }) => { + return `${params?.lang ? `/${params?.lang}`: ''}/gp/one` + }, + "gp_two": (params?: { lang?: ('fr' | 'en' | 'hu' | 'at' | string) }) => { + return `${params?.lang ? `/${params?.lang}`: ''}/gp/two` + }, + "main": (params?: { lang?: ('fr' | 'en' | 'hu' | 'at' | string) }) => { + return `${params?.lang ? `/${params?.lang}`: ''}/main` + }, + "match_id_int": (params: { id: (string | number), lang?: ('fr' | 'en' | 'hu' | 'at' | string) }) => { + return `${params?.lang ? `/${params?.lang}`: ''}/match/${params.id}` + }, + "site": (params?: { lang?: ('fr' | 'en' | 'hu' | 'at' | string), limit?: (number) }, sp?: Record) => { + return `${params?.lang ? `/${params?.lang}`: ''}/site${appendSp({ limit: params?.limit, ...sp })}` + }, + "site_id": (params?: { lang?: ('fr' | 'hu' | undefined), id?: (string), limit?: (number), demo?: (string) }) => { + params = params ?? {} + params.lang = params.lang ?? "fr"; + params.id = params.id ?? "Vienna"; + return `${params?.lang ? `/${params?.lang}`: ''}/site/${params.id}${appendSp({ limit: params?.limit, demo: params?.demo })}` + }, + "site_contract_siteId_contractId": (params: { siteId: (string | number), contractId: (string | number), lang?: ('fr' | 'en' | 'hu' | 'at' | string), limit?: (number) }) => { + return `${params?.lang ? `/${params?.lang}`: ''}/site_contract/${params.siteId}-${params.contractId}${appendSp({ limit: params?.limit })}` + }, + "a_rest_z": (params: { rest: (string | number)[] }) => { + return `/a/${params.rest?.join('/')}/z` + }, + "lay_normal": `/lay/normal`, + "lay_root_layout": `/lay/root-layout`, + "lay_skip": `/lay/skip` +} + +/** + * SERVERS + */ +const SERVERS = { + "GET contract": (params?: { lang?: ('fr' | 'en' | 'hu' | 'at' | string) }) => { + return `${params?.lang ? `/${params?.lang}`: ''}/contract` + }, + "POST contract": (params?: { lang?: ('fr' | 'en' | 'hu' | 'at' | string) }) => { + return `${params?.lang ? `/${params?.lang}`: ''}/contract` + }, + "GET site": (params?: { lang?: ('fr' | 'en' | 'hu' | 'at' | string) }) => { + return `${params?.lang ? `/${params?.lang}`: ''}/site` + }, + "GET api_graphql": `/api/graphql`, + "POST api_graphql": `/api/graphql` +} + +/** + * ACTIONS + */ +const ACTIONS = { + "default contract_id": (params: { id: (string | number), lang?: ('fr' | 'en' | 'hu' | 'at' | string), limit?: (number) }) => { + return `${params?.lang ? `/${params?.lang}`: ''}/contract/${params.id}${appendSp({ limit: params?.limit })}` + }, + "create site": (params?: { lang?: ('fr' | 'en' | 'hu' | 'at' | string) }) => { + return `${params?.lang ? `/${params?.lang}`: ''}/site?/create` + }, + "update site_id": (params: { id: (string | number), lang?: ('fr' | 'en' | 'hu' | 'at' | string) }) => { + return `${params?.lang ? `/${params?.lang}`: ''}/site/${params.id}?/update` + }, + "delete site_id": (params: { id: (string | number), lang?: ('fr' | 'en' | 'hu' | 'at' | string) }) => { + return `${params?.lang ? `/${params?.lang}`: ''}/site/${params.id}?/delete` + }, + "noSatisfies site_contract": (params?: { lang?: ('fr' | 'en' | 'hu' | 'at' | string) }) => { + return `${params?.lang ? `/${params?.lang}`: ''}/site_contract?/noSatisfies` + }, + "send site_contract_siteId_contractId": (params: { siteId: (string | number), contractId: (string | number), lang?: ('fr' | 'en' | 'hu' | 'at' | string), extra?: ('A' | 'B') }) => { + params.extra = params.extra ?? "A"; + return `${params?.lang ? `/${params?.lang}`: ''}/site_contract/${params.siteId}-${params.contractId}?/send${appendSp({ extra: params?.extra }, '&')}` + } +} + +/** + * LINKS + */ +const LINKS = { + "twitter": `https:/twitter.com/jycouet`, + "twitter_post": (params: { name: (string | number), id: (string | number) }) => { + return `https:/twitter.com/${params.name}/status/${params.id}` + }, + "gravatar": (params: { str: (string | number), s?: (number), d?: ("retro" | "identicon") }) => { + params.s = params.s ?? 75; + params.d = params.d ?? "identicon"; + return `https:/www.gravatar.com/avatar/${params.str}${appendSp({ s: params?.s, d: params?.d })}` + } +} + +/** + * Append search params to a string + */ +const appendSp = (sp?: Record, prefix: '?' | '&' = '?') => { + if (sp === undefined) return '' + const mapping = Object.entries(sp) + .filter(c => c[1] !== undefined) + .map(c => [c[0], String(c[1])]) + + const formated = new URLSearchParams(mapping).toString() + if (formated) { + return `${prefix}${formated}` + } + return '' +} + +// route function helpers +type NonFunctionKeys = { [K in keyof T]: T[K] extends Function ? never : K }[keyof T] +type FunctionKeys = { [K in keyof T]: T[K] extends Function ? K : never }[keyof T] +type FunctionParams = T extends (...args: infer P) => any ? P : never + +const AllObjs = { ...PAGES, ...ACTIONS, ...SERVERS, ...LINKS } +type AllTypes = typeof AllObjs + +/** + * To be used like this: + * ```ts + * import { route } from '$lib/ROUTES' + * + * route('site_id', { id: 1 }) + * ``` + */ +export function route>(key: T, ...params: FunctionParams): string +export function route>(key: T): string +export function route(key: T, ...params: any[]): string { + if (AllObjs[key] instanceof Function) { + const element = (AllObjs as any)[key] as (...args: any[]) => string + return element(...params) + } else { + return AllObjs[key] as string + } +} + +/** +* Add this type as a generic of the vite plugin `kitRoutes`. +* +* Full example: +* ```ts +* import type { KIT_ROUTES } from '$lib/ROUTES' +* import { kitRoutes } from 'vite-plugin-kit-routes' +* +* kitRoutes({ +* PAGES: { +* // here, key of object will be typed! +* } +* }) +* ``` +*/ +export type KIT_ROUTES = { + PAGES: { '_ROOT': never, 'subGroup': never, 'subGroup2': never, 'contract': 'lang', 'contract_id': 'id' | 'lang', 'gp_one': 'lang', 'gp_two': 'lang', 'main': 'lang', 'match_id_int': 'id' | 'lang', 'site': 'lang', 'site_id': 'lang' | 'id', 'site_contract_siteId_contractId': 'siteId' | 'contractId' | 'lang', 'a_rest_z': 'rest', 'lay_normal': never, 'lay_root_layout': never, 'lay_skip': never } + SERVERS: { 'GET contract': 'lang', 'POST contract': 'lang', 'GET site': 'lang', 'GET api_graphql': never, 'POST api_graphql': never } + ACTIONS: { 'default contract_id': 'id' | 'lang', 'create site': 'lang', 'update site_id': 'id' | 'lang', 'delete site_id': 'id' | 'lang', 'noSatisfies site_contract': 'lang', 'send site_contract_siteId_contractId': 'siteId' | 'contractId' | 'lang' } + LINKS: { 'twitter': never, 'twitter_post': 'name' | 'id', 'gravatar': 'str' } + Params: { first: never, lang: never, id: never, limit: never, demo: never, siteId: never, contractId: never, rest: never, extra: never, name: never, str: never, s: never, d: never } +} diff --git a/packages/vite-plugin-kit-routes/src/test/ROUTES_format-variables.ts b/packages/vite-plugin-kit-routes/src/test/ROUTES_format-variables.ts new file mode 100644 index 000000000..0c3d6aa7b --- /dev/null +++ b/packages/vite-plugin-kit-routes/src/test/ROUTES_format-variables.ts @@ -0,0 +1,140 @@ +/** + * This file was generated by 'vite-plugin-kit-routes' + * + * >> DO NOT EDIT THIS FILE MANUALLY << + */ + +/** + * PAGES + */ +export const PAGE__ROOT = `/` +export const PAGE_subGroup = `/subGroup` +export const PAGE_subGroup2 = (params: { first: (string | number) }) => { + return `/subGroup2${appendSp({ first: params?.first })}` +} +export const PAGE_contract = (params?: { lang?: ('fr' | 'en' | 'hu' | 'at' | string) }, sp?: Record) => { + return `${params?.lang ? `/${params?.lang}`: ''}/contract${appendSp(sp)}` +} +export const PAGE_contract_id = (params: { id: (string | number), lang?: ('fr' | 'en' | 'hu' | 'at' | string) }) => { + return `${params?.lang ? `/${params?.lang}`: ''}/contract/${params.id}` +} +export const PAGE_gp_one = (params?: { lang?: ('fr' | 'en' | 'hu' | 'at' | string) }) => { + return `${params?.lang ? `/${params?.lang}`: ''}/gp/one` +} +export const PAGE_gp_two = (params?: { lang?: ('fr' | 'en' | 'hu' | 'at' | string) }) => { + return `${params?.lang ? `/${params?.lang}`: ''}/gp/two` +} +export const PAGE_main = (params?: { lang?: ('fr' | 'en' | 'hu' | 'at' | string) }) => { + return `${params?.lang ? `/${params?.lang}`: ''}/main` +} +export const PAGE_match_id_int = (params: { id: (string | number), lang?: ('fr' | 'en' | 'hu' | 'at' | string) }) => { + return `${params?.lang ? `/${params?.lang}`: ''}/match/${params.id}` +} +export const PAGE_site = (params?: { lang?: ('fr' | 'en' | 'hu' | 'at' | string), limit?: (number) }, sp?: Record) => { + return `${params?.lang ? `/${params?.lang}`: ''}/site${appendSp({ limit: params?.limit, ...sp })}` +} +export const PAGE_site_id = (params?: { lang?: ('fr' | 'hu' | undefined), id?: (string), limit?: (number), demo?: (string) }) => { + params = params ?? {} + params.lang = params.lang ?? "fr"; + params.id = params.id ?? "Vienna"; + return `${params?.lang ? `/${params?.lang}`: ''}/site/${params.id}${appendSp({ limit: params?.limit, demo: params?.demo })}` +} +export const PAGE_site_contract_siteId_contractId = (params: { siteId: (string | number), contractId: (string | number), lang?: ('fr' | 'en' | 'hu' | 'at' | string), limit?: (number) }) => { + return `${params?.lang ? `/${params?.lang}`: ''}/site_contract/${params.siteId}-${params.contractId}${appendSp({ limit: params?.limit })}` +} +export const PAGE_a_rest_z = (params: { rest: (string | number)[] }) => { + return `/a/${params.rest?.join('/')}/z` +} +export const PAGE_lay_normal = `/lay/normal` +export const PAGE_lay_root_layout = `/lay/root-layout` +export const PAGE_lay_skip = `/lay/skip` + +/** + * SERVERS + */ +export const SERVER_GET_contract = (params?: { lang?: ('fr' | 'en' | 'hu' | 'at' | string) }) => { + return `${params?.lang ? `/${params?.lang}`: ''}/contract` +} +export const SERVER_POST_contract = (params?: { lang?: ('fr' | 'en' | 'hu' | 'at' | string) }) => { + return `${params?.lang ? `/${params?.lang}`: ''}/contract` +} +export const SERVER_GET_site = (params?: { lang?: ('fr' | 'en' | 'hu' | 'at' | string) }) => { + return `${params?.lang ? `/${params?.lang}`: ''}/site` +} +export const SERVER_GET_api_graphql = `/api/graphql` +export const SERVER_POST_api_graphql = `/api/graphql` + +/** + * ACTIONS + */ +export const ACTION_default_contract_id = (params: { id: (string | number), lang?: ('fr' | 'en' | 'hu' | 'at' | string), limit?: (number) }) => { + return `${params?.lang ? `/${params?.lang}`: ''}/contract/${params.id}${appendSp({ limit: params?.limit })}` +} +export const ACTION_create_site = (params?: { lang?: ('fr' | 'en' | 'hu' | 'at' | string) }) => { + return `${params?.lang ? `/${params?.lang}`: ''}/site?/create` +} +export const ACTION_update_site_id = (params: { id: (string | number), lang?: ('fr' | 'en' | 'hu' | 'at' | string) }) => { + return `${params?.lang ? `/${params?.lang}`: ''}/site/${params.id}?/update` +} +export const ACTION_delete_site_id = (params: { id: (string | number), lang?: ('fr' | 'en' | 'hu' | 'at' | string) }) => { + return `${params?.lang ? `/${params?.lang}`: ''}/site/${params.id}?/delete` +} +export const ACTION_noSatisfies_site_contract = (params?: { lang?: ('fr' | 'en' | 'hu' | 'at' | string) }) => { + return `${params?.lang ? `/${params?.lang}`: ''}/site_contract?/noSatisfies` +} +export const ACTION_send_site_contract_siteId_contractId = (params: { siteId: (string | number), contractId: (string | number), lang?: ('fr' | 'en' | 'hu' | 'at' | string), extra?: ('A' | 'B') }) => { + params.extra = params.extra ?? "A"; + return `${params?.lang ? `/${params?.lang}`: ''}/site_contract/${params.siteId}-${params.contractId}?/send${appendSp({ extra: params?.extra }, '&')}` +} + +/** + * LINKS + */ +export const LINK_twitter = `https:/twitter.com/jycouet` +export const LINK_twitter_post = (params: { name: (string | number), id: (string | number) }) => { + return `https:/twitter.com/${params.name}/status/${params.id}` +} +export const LINK_gravatar = (params: { str: (string | number), s?: (number), d?: ("retro" | "identicon") }) => { + params.s = params.s ?? 75; + params.d = params.d ?? "identicon"; + return `https:/www.gravatar.com/avatar/${params.str}${appendSp({ s: params?.s, d: params?.d })}` +} + +/** + * Append search params to a string + */ +const appendSp = (sp?: Record, prefix: '?' | '&' = '?') => { + if (sp === undefined) return '' + const mapping = Object.entries(sp) + .filter(c => c[1] !== undefined) + .map(c => [c[0], String(c[1])]) + + const formated = new URLSearchParams(mapping).toString() + if (formated) { + return `${prefix}${formated}` + } + return '' +} + +/** +* Add this type as a generic of the vite plugin `kitRoutes`. +* +* Full example: +* ```ts +* import type { KIT_ROUTES } from '$lib/ROUTES' +* import { kitRoutes } from 'vite-plugin-kit-routes' +* +* kitRoutes({ +* PAGES: { +* // here, key of object will be typed! +* } +* }) +* ``` +*/ +export type KIT_ROUTES = { + PAGES: { '_ROOT': never, 'subGroup': never, 'subGroup2': never, 'contract': 'lang', 'contract_id': 'id' | 'lang', 'gp_one': 'lang', 'gp_two': 'lang', 'main': 'lang', 'match_id_int': 'id' | 'lang', 'site': 'lang', 'site_id': 'lang' | 'id', 'site_contract_siteId_contractId': 'siteId' | 'contractId' | 'lang', 'a_rest_z': 'rest', 'lay_normal': never, 'lay_root_layout': never, 'lay_skip': never } + SERVERS: { 'GET_contract': 'lang', 'POST_contract': 'lang', 'GET_site': 'lang', 'GET_api_graphql': never, 'POST_api_graphql': never } + ACTIONS: { 'default_contract_id': 'id' | 'lang', 'create_site': 'lang', 'update_site_id': 'id' | 'lang', 'delete_site_id': 'id' | 'lang', 'noSatisfies_site_contract': 'lang', 'send_site_contract_siteId_contractId': 'siteId' | 'contractId' | 'lang' } + LINKS: { 'twitter': never, 'twitter_post': 'name' | 'id', 'gravatar': 'str' } + Params: { first: never, lang: never, id: never, limit: never, demo: never, siteId: never, contractId: never, rest: never, extra: never, name: never, str: never, s: never, d: never } +} diff --git a/packages/vite-plugin-kit-routes/src/test/ROUTES_post-update.ts b/packages/vite-plugin-kit-routes/src/test/ROUTES_post-update.ts new file mode 100644 index 000000000..94c47b41f --- /dev/null +++ b/packages/vite-plugin-kit-routes/src/test/ROUTES_post-update.ts @@ -0,0 +1,161 @@ +/** + * This file was generated by 'vite-plugin-kit-routes' + * + * >> DO NOT EDIT THIS FILE MANUALLY << + */ + +/** + * PAGES + */ +const PAGES = { + "/": `/`, + "/subGroup": `/subGroup`, + "/subGroup2": `/subGroup2`, + "/contract": (params?: { lang?: (string | number) }) => { + return `${params?.lang ? `/${params?.lang}`: ''}/contract` + }, + "/contract/[id]": (params: { id: (string | number), lang?: (string | number) }) => { + return `${params?.lang ? `/${params?.lang}`: ''}/contract/${params.id}` + }, + "/gp/one": (params?: { lang?: (string | number) }) => { + return `${params?.lang ? `/${params?.lang}`: ''}/gp/one` + }, + "/gp/two": (params?: { lang?: (string | number) }) => { + return `${params?.lang ? `/${params?.lang}`: ''}/gp/two` + }, + "/main": (params?: { lang?: (string | number) }) => { + return `${params?.lang ? `/${params?.lang}`: ''}/main` + }, + "/match/[id=int]": (params: { id: (string | number), lang?: (string | number) }) => { + return `${params?.lang ? `/${params?.lang}`: ''}/match/${params.id}` + }, + "/site": (params?: { lang?: (string | number) }) => { + return `${params?.lang ? `/${params?.lang}`: ''}/site` + }, + "/site/[id]": (params: { id: (string | number), lang?: (string | number) }) => { + return `${params?.lang ? `/${params?.lang}`: ''}/site/${params.id}` + }, + "/site_contract/[siteId]-[contractId]": (params: { siteId: (string | number), contractId: (string | number), lang?: (string | number) }) => { + return `${params?.lang ? `/${params?.lang}`: ''}/site_contract/${params.siteId}-${params.contractId}` + }, + "/a/[...rest]/z": (params: { rest: (string | number)[] }) => { + return `/a/${params.rest?.join('/')}/z` + }, + "/lay/normal": `/lay/normal`, + "/lay/root-layout": `/lay/root-layout`, + "/lay/skip": `/lay/skip` +} + +/** + * SERVERS + */ +const SERVERS = { + "GET /contract": (params?: { lang?: (string | number) }) => { + return `${params?.lang ? `/${params?.lang}`: ''}/contract` + }, + "POST /contract": (params?: { lang?: (string | number) }) => { + return `${params?.lang ? `/${params?.lang}`: ''}/contract` + }, + "GET /site": (params?: { lang?: (string | number) }) => { + return `${params?.lang ? `/${params?.lang}`: ''}/site` + }, + "GET /api/graphql": `/api/graphql`, + "POST /api/graphql": `/api/graphql` +} + +/** + * ACTIONS + */ +const ACTIONS = { + "default /contract/[id]": (params: { id: (string | number), lang?: (string | number) }) => { + return `${params?.lang ? `/${params?.lang}`: ''}/contract/${params.id}` + }, + "create /site": (params?: { lang?: (string | number) }) => { + return `${params?.lang ? `/${params?.lang}`: ''}/site?/create` + }, + "update /site/[id]": (params: { id: (string | number), lang?: (string | number) }) => { + return `${params?.lang ? `/${params?.lang}`: ''}/site/${params.id}?/update` + }, + "delete /site/[id]": (params: { id: (string | number), lang?: (string | number) }) => { + return `${params?.lang ? `/${params?.lang}`: ''}/site/${params.id}?/delete` + }, + "noSatisfies /site_contract": (params?: { lang?: (string | number) }) => { + return `${params?.lang ? `/${params?.lang}`: ''}/site_contract?/noSatisfies` + }, + "send /site_contract/[siteId]-[contractId]": (params: { siteId: (string | number), contractId: (string | number), lang?: (string | number) }) => { + return `${params?.lang ? `/${params?.lang}`: ''}/site_contract/${params.siteId}-${params.contractId}?/send` + } +} + +/** + * LINKS + */ +const LINKS = { + +} + +/** + * Append search params to a string + */ +const appendSp = (sp?: Record, prefix: '?' | '&' = '?') => { + if (sp === undefined) return '' + const mapping = Object.entries(sp) + .filter(c => c[1] !== undefined) + .map(c => [c[0], String(c[1])]) + + const formated = new URLSearchParams(mapping).toString() + if (formated) { + return `${prefix}${formated}` + } + return '' +} + +// route function helpers +type NonFunctionKeys = { [K in keyof T]: T[K] extends Function ? never : K }[keyof T] +type FunctionKeys = { [K in keyof T]: T[K] extends Function ? K : never }[keyof T] +type FunctionParams = T extends (...args: infer P) => any ? P : never + +const AllObjs = { ...PAGES, ...ACTIONS, ...SERVERS, ...LINKS } +type AllTypes = typeof AllObjs + +/** + * To be used like this: + * ```ts + * import { route } from '$lib/ROUTES' + * + * route('site_id', { id: 1 }) + * ``` + */ +export function route>(key: T, ...params: FunctionParams): string +export function route>(key: T): string +export function route(key: T, ...params: any[]): string { + if (AllObjs[key] instanceof Function) { + const element = (AllObjs as any)[key] as (...args: any[]) => string + return element(...params) + } else { + return AllObjs[key] as string + } +} + +/** +* Add this type as a generic of the vite plugin `kitRoutes`. +* +* Full example: +* ```ts +* import type { KIT_ROUTES } from '$lib/ROUTES' +* import { kitRoutes } from 'vite-plugin-kit-routes' +* +* kitRoutes({ +* PAGES: { +* // here, key of object will be typed! +* } +* }) +* ``` +*/ +export type KIT_ROUTES = { + PAGES: { '/': never, '/subGroup': never, '/subGroup2': never, '/contract': 'lang', '/contract/[id]': 'id' | 'lang', '/gp/one': 'lang', '/gp/two': 'lang', '/main': 'lang', '/match/[id=int]': 'id' | 'lang', '/site': 'lang', '/site/[id]': 'id' | 'lang', '/site_contract/[siteId]-[contractId]': 'siteId' | 'contractId' | 'lang', '/a/[...rest]/z': 'rest', '/lay/normal': never, '/lay/root-layout': never, '/lay/skip': never } + SERVERS: { 'GET /contract': 'lang', 'POST /contract': 'lang', 'GET /site': 'lang', 'GET /api/graphql': never, 'POST /api/graphql': never } + ACTIONS: { 'default /contract/[id]': 'id' | 'lang', 'create /site': 'lang', 'update /site/[id]': 'id' | 'lang', 'delete /site/[id]': 'id' | 'lang', 'noSatisfies /site_contract': 'lang', 'send /site_contract/[siteId]-[contractId]': 'siteId' | 'contractId' | 'lang' } + LINKS: Record + Params: { lang: never, id: never, siteId: never, contractId: never, rest: never } +} diff --git a/packages/vite-plugin-kit-routes/vite.config.ts b/packages/vite-plugin-kit-routes/vite.config.ts index 59d7f5bb6..4fd262c52 100644 --- a/packages/vite-plugin-kit-routes/vite.config.ts +++ b/packages/vite-plugin-kit-routes/vite.config.ts @@ -4,13 +4,18 @@ import { defineConfig } from 'vite' import { kitRoutes } from './src/lib/index.js' +// export function route(key: 'subGroup2', params: { first: string | number }): string +// export function route(key: '_ROOT'): string +// export function route(key: any, ...params: any): string { + export default defineConfig({ plugins: [ sveltekit(), // demo kitRoutes({ - format: '_', - logs: ['update', 'errors'], + // format: '_', + format: 'object[symbol]', + // exclude_logs: ['update', 'errors', 'stats', 'post_update_run'], // path_base: true, // default_type: 'string', // extra_search_params: 'with', @@ -25,21 +30,21 @@ export default defineConfig({ }, }, }, - lang_site: { + site: { extra_search_params: 'with', explicit_search_params: { limit: { type: 'number' } }, params: { // yop: { type: 'number' }, }, }, - lang_site_id: { + site_id: { explicit_search_params: { limit: { type: 'number' }, demo: { type: 'string' } }, params: { id: { type: 'string', default: '"Vienna"' }, lang: { type: "'fr' | 'hu' | undefined", default: '"fr"' }, }, }, - lang_site_contract_siteId_contractId: { + site_contract_siteId_contractId: { explicit_search_params: { limit: { type: 'number' } }, }, }, @@ -50,16 +55,21 @@ export default defineConfig({ // yop: {}, }, ACTIONS: { - lang_contract_id: { + default_contract_id: { explicit_search_params: { limit: { type: 'number' }, }, }, - lang_site_contract_siteId_contractId: { + send_site_contract_siteId_contractId: { explicit_search_params: { extra: { type: "'A' | 'B'", default: '"A"' }, }, }, + create_site: { + explicit_search_params: { + redirectTo: { type: '"list" | "new" | "detail"' }, + }, + }, }, LINKS: { // reference to a hardcoded link diff --git a/website/src/pages/docs/tools/06_vite-plugin-kit-routes.mdx b/website/src/pages/docs/tools/06_vite-plugin-kit-routes.mdx index 6d232dede..c9ab87229 100644 --- a/website/src/pages/docs/tools/06_vite-plugin-kit-routes.mdx +++ b/website/src/pages/docs/tools/06_vite-plugin-kit-routes.mdx @@ -6,70 +6,67 @@ import { Callout } from '@theguild/components' large applications where manual tracking of route changes is error-prone. It simplifies development by ensuring all route links are consistent and up-to-date, saving time and preventing broken links. -You will essentially have 4 objects at your disposal: `PAGES`, `SERVERS`, `ACTIONS` and `LINKS` and -instead of hardcode strings, you will use these objects that are always up to date with your routes. - No more 🤞, now be **confident** ✅! -By default, no Configuration is requiered. Just [Install](#installation) the plugin, and use objects -available in your `$lib/ROUTES.ts` generated file _(always in sync)_. +By default, no Configuration is requiered. Just [Install](#installation) the plugin, and use +objects/functions available in your `$lib/ROUTES.ts` generated file _(always in sync)_. ## Examples -```svelte filename="PAGES - First example" {2, 9} +```svelte filename="First example" {2, 9} Terms -Terms +Terms ``` -```svelte filename="PAGES - With 1 route param" {2, 9} +```svelte filename="With 1 route param" {2, 9} Go to site -Go to site +Go to site ``` -```svelte filename="PAGES - With 1 Search Param*" {2, 9} +```svelte filename="With 1 Search Param*" {2, 9} Go to site -Go to site +Go to site ``` -```svelte filename="ACTIONS - With a named action" {4, 12} +```svelte filename="With a named action" {4, 12}
    @@ -79,26 +76,26 @@ available in your `$lib/ROUTES.ts` generated file _(always in sync)_. ```svelte filename="LINKS - 1 reference in config*" {2, 9} Twitter -Twitter +Twitter ``` ```svelte filename="LINKS - with params & search params*" {2, 9} logo -logo +logo ```
    @@ -154,18 +151,35 @@ your `+page.svelte` | `+server.ts` | `+page.server.ts`. --- -- What kind of person are you: `PAGES['/info/terms']` or `PAGES.info_terms` ? +- What kind of format you want to use ? _You can choose anyway_ 😜 ```ts filename="vite.config.ts" kitRoutes({ - // format: '/' (default) - format: '_' + /** + * // format: route(path) -> default <- + * route("/site/[id]", { id: 7, tab: 'info' }) + * + * // format: route(symbol) + * route("site_id", { id: 7, tab: 'info' }) + * + * // format: `variables` (best for code splitting & privacy) + * PAGE_site_id({ id: 7, tab: 'info' }) + * + * // format: object[path] + * PAGES["/site/[id]"]({ id: 7, tab: 'info' }) + * + * // format: object[symbol] + * PAGES.site_id({ id: 7, tab: 'info' }) + */ + format: 'route(path)' }) ``` -- You like nice and formated files? You can add **any** cmd after the generation +- You like nice and formated files? You can add **any** cmd after the generation. + +_Here is an example of prettier_ ```ts filename="vite.config.ts" kitRoutes({ @@ -178,7 +192,7 @@ kitRoutes({ ```ts filename="vite.config.ts" kitRoutes({ PAGES: { - site: { + '/site': { explicit_search_params: { limit: { type: 'number' } } @@ -193,7 +207,7 @@ kitRoutes({ ```ts filename="vite.config.ts" kitRoutes({ PAGES: { - site_id: { + '/site/[id]': { params: { id: { type: 'string' } } @@ -214,27 +228,6 @@ kitRoutes({ }) ``` -- You are concerned about `code splitting` and/or `privacy` ? Let's generate variables instead of - objects! - -```ts filename="vite.config.ts" -kitRoutes({ - format: 'variables' -}) -``` - -```ts filename="src/lib/ROUTES.ts" {7-9} -// With format: '_', it was like 👇 -// export const PAGES: { -// main: '/main', -// about '/about' -// } - -// With format: 'variables' it's like 👇 -export const PAGES_main = '/main' -export const PAGES_about '/about' -``` - - You want to use some `LINKS` in different places in your app? Let's show you what we can do: ```ts filename="vite.config.ts"