arkormx 0.1.7 → 0.1.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.mjs CHANGED
@@ -1,33 +1,1594 @@
1
1
  #!/usr/bin/env node
2
- import{copyFileSync as e,existsSync as t,mkdirSync as n,readFileSync as r,readdirSync as i,rmSync as a,writeFileSync as o}from"fs";import s,{dirname as c,extname as l,join as u,relative as d}from"path";import{createRequire as f}from"module";import{existsSync as p,mkdirSync as m,readFileSync as h,readdirSync as g,writeFileSync as _}from"node:fs";import{join as v,resolve as y}from"node:path";import{spawnSync as ee}from"node:child_process";import{str as b}from"@h3ravel/support";import{fileURLToPath as te,pathToFileURL as ne}from"url";import{Logger as x}from"@h3ravel/shared";import{Command as S,Kernel as C}from"@h3ravel/musket";import{pathToFileURL as w}from"node:url";var T=class extends Error{constructor(e){super(e),this.name=`ArkormException`}},E=class{},D=class{columns=[];dropColumnNames=[];indexes=[];latestColumnName;primary(e,t){let n=typeof e==`string`?{columnName:e,...t??{}}:e??{},r=this.resolveColumn(n.columnName);return r.primary=!0,typeof n.autoIncrement==`boolean`&&(r.autoIncrement=n.autoIncrement),Object.prototype.hasOwnProperty.call(n,`default`)&&(r.default=n.default),this}id(e=`id`,t=`id`){return this.column(e,t,{primary:!0})}uuid(e,t={}){return this.column(e,`uuid`,t)}string(e,t={}){return this.column(e,`string`,t)}text(e,t={}){return this.column(e,`text`,t)}integer(e,t={}){return this.column(e,`integer`,t)}bigInteger(e,t={}){return this.column(e,`bigInteger`,t)}float(e,t={}){return this.column(e,`float`,t)}boolean(e,t={}){return this.column(e,`boolean`,t)}json(e,t={}){return this.column(e,`json`,t)}date(e,t={}){return this.column(e,`date`,t)}morphs(e,t=!1){return this.string(`${e}Type`,{nullable:t}),this.integer(`${e}Id`,{nullable:t}),this}nullableMorphs(e){return this.morphs(e,!0)}timestamp(e,t={}){return this.column(e,`timestamp`,t)}timestamps(){return this.timestamp(`createdAt`,{nullable:!1}),this.timestamp(`updatedAt`,{nullable:!1}),this}softDeletes(e=`deletedAt`){return this.timestamp(e,{nullable:!0}),this}dropColumn(e){return this.dropColumnNames.push(e),this}nullable(e){let t=this.resolveColumn(e);return t.nullable=!0,this}after(e,t){let n=this.resolveColumn(t);return n.after=e,this}map(e,t){let n=this.resolveColumn(t);return n.map=e,this}index(e,t){let n=Array.isArray(e)?e:typeof e==`string`?[e]:[this.resolveColumn().name];return this.indexes.push({columns:[...n],name:t}),this}getColumns(){return this.columns.map(e=>({...e}))}getDropColumns(){return[...this.dropColumnNames]}getIndexes(){return this.indexes.map(e=>({...e,columns:[...e.columns]}))}column(e,t,n){return this.columns.push({name:e,type:t,map:n.map,nullable:n.nullable,unique:n.unique,primary:n.primary,autoIncrement:n.autoIncrement,after:n.after,default:n.default}),this.latestColumnName=e,this}resolveColumn(e){let t=e??this.latestColumnName;if(!t)throw Error(`No column available for this operation.`);let n=this.columns.find(e=>e.name===t);if(!n)throw Error(`Column [${t}] was not found in the table definition.`);return n}},re=class{operations=[];createTable(e,t){let n=new D;return t(n),this.operations.push({type:`createTable`,table:e,columns:n.getColumns(),indexes:n.getIndexes()}),this}alterTable(e,t){let n=new D;return t(n),this.operations.push({type:`alterTable`,table:e,addColumns:n.getColumns(),dropColumns:n.getDropColumns(),addIndexes:n.getIndexes()}),this}dropTable(e){return this.operations.push({type:`dropTable`,table:e}),this}getOperations(){return this.operations.map(e=>e.type===`createTable`?{...e,columns:e.columns.map(e=>({...e})),indexes:e.indexes.map(e=>({...e,columns:[...e.columns]}))}:e.type===`alterTable`?{...e,addColumns:e.addColumns.map(e=>({...e})),dropColumns:[...e.dropColumns],addIndexes:e.addIndexes.map(e=>({...e,columns:[...e.columns]}))}:{...e})}};const ie=/model\s+(\w+)\s*\{[\s\S]*?\n\}/g,O=e=>{let t=e.replace(/[^a-zA-Z0-9]+/g,` `).trim(),n=(t.endsWith(`s`)&&t.length>1?t.slice(0,-1):t).split(/\s+/g).filter(Boolean);return n.length===0?`GeneratedModel`:n.map(e=>`${e.charAt(0).toUpperCase()}${e.slice(1)}`).join(``)},k=e=>e.replace(/[.*+?^${}()|[\]\\]/g,`\\$&`),ae=e=>e.type===`id`?`Int`:e.type===`uuid`||e.type===`string`||e.type===`text`?`String`:e.type===`integer`?`Int`:e.type===`bigInteger`?`BigInt`:e.type===`float`?`Float`:e.type===`boolean`?`Boolean`:e.type===`json`?`Json`:`DateTime`,A=e=>{if(e!=null){if(typeof e==`string`)return`@default("${e.replace(/"/g,`\\"`)}")`;if(typeof e==`number`||typeof e==`bigint`)return`@default(${e})`;if(typeof e==`boolean`)return`@default(${e?`true`:`false`})`}},j=e=>{if(e.type===`id`){let t=e.primary===!1?``:` @id`,n=typeof e.map==`string`&&e.map.trim().length>0?` @map("${e.map.replace(/"/g,`\\"`)}")`:``,r=A(e.default),i=e.autoIncrement??e.primary!==!1,a=r?` ${r}`:i&&t?` @default(autoincrement())`:``;return` ${e.name} Int${t}${a}${n}`}let t=ae(e),n=e.nullable?`?`:``,r=e.unique?` @unique`:``,i=e.primary?` @id`:``,a=typeof e.map==`string`&&e.map.trim().length>0?` @map("${e.map.replace(/"/g,`\\"`)}")`:``,o=A(e.default)??(e.type===`uuid`&&e.primary?`@default(uuid())`:void 0),s=o?` ${o}`:``;return` ${e.name} ${t}${n}${i}${r}${s}${a}`},M=e=>` @@index([${e.columns.join(`, `)}]${typeof e.name==`string`&&e.name.trim().length>0?`, name: "${e.name.replace(/"/g,`\\"`)}"`:``})`,N=e=>{let t=O(e.table),n=e.table!==t.toLowerCase(),r=e.columns.map(j),i=[...(e.indexes??[]).map(M),...n?[` @@map("${b(e.table).snake()}")`]:[]];return`model ${t} {\n${(i.length>0?[...r,``,...i]:r).join(`
3
- `)}\n}`},P=(e,t)=>{let n=[...e.matchAll(ie)],r=RegExp(`@@map\\("${k(t)}"\\)`);for(let e of n){let n=e[0],i=e[1],a=e.index??0,o=a+n.length;if(r.test(n)||i.toLowerCase()===t.toLowerCase()||i.toLowerCase()===O(t).toLowerCase())return{modelName:i,block:n,start:a,end:o}}return null},F=(e,t)=>{if(P(e,t.table))throw new T(`Prisma model for table [${t.table}] already exists.`);let n=N(t);return`${e.trimEnd()}\n\n${n}\n`},oe=(e,t)=>{let n=P(e,t.table);if(!n)throw new T(`Prisma model for table [${t.table}] was not found.`);let r=n.block,i=r.split(`
4
- `);return t.dropColumns.forEach(e=>{let t=RegExp(`^\\s*${k(e)}\\s+`);for(let e=0;e<i.length;e+=1)if(t.test(i[e])){i.splice(e,1);return}}),t.addColumns.forEach(e=>{let t=j(e),n=RegExp(`^\\s*${k(e.name)}\\s+`);if(i.some(e=>n.test(e)))return;let r=Math.max(1,i.length-1),a=typeof e.after==`string`&&e.after.length>0?i.findIndex(t=>RegExp(`^\\s*${k(e.after)}\\s+`).test(t)):-1,o=a>0?Math.min(a+1,r):r;i.splice(o,0,t)}),(t.addIndexes??[]).forEach(e=>{let t=M(e);if(i.some(e=>e.trim()===t.trim()))return;let n=Math.max(1,i.length-1);i.splice(n,0,t)}),r=i.join(`
5
- `),`${e.slice(0,n.start)}${r}${e.slice(n.end)}`},se=(e,t)=>{let n=P(e,t.table);if(!n)return e;let r=e.slice(0,n.start).trimEnd(),i=e.slice(n.end).trimStart();return`${r}${r&&i?`
6
-
7
- `:``}${i}`},ce=(e,t)=>t.reduce((e,t)=>t.type===`createTable`?F(e,t):t.type===`alterTable`?oe(e,t):se(e,t),e),I=(e,t)=>{let n=ee(`npx`,[`prisma`,...e],{cwd:t,encoding:`utf-8`});if(n.status===0)return;let r=[n.stdout,n.stderr].filter(Boolean).join(`
8
- `).trim();throw new T(r?`Prisma command failed: prisma ${e.join(` `)}\n${r}`:`Prisma command failed: prisma ${e.join(` `)}`)},L=e=>{let t=e.replace(/[^a-zA-Z0-9]+/g,` `).trim();return t?`${t.split(/\s+/g).map(e=>`${e.charAt(0).toUpperCase()}${e.slice(1)}`).join(``)}Migration`:`GeneratedMigration`},R=e=>String(e).padStart(2,`0`),z=(e=new Date)=>`${e.getFullYear()}${R(e.getMonth()+1)}${R(e.getDate())}${R(e.getHours())}${R(e.getMinutes())}${R(e.getSeconds())}`,B=e=>e.trim().toLowerCase().replace(/[^a-z0-9]+/g,`_`).replace(/^_+|_+$/g,``)||`migration`,V=(e,t=`ts`)=>t===`js`?[`import { Migration } from 'arkormx'`,``,`export default class ${e} extends Migration {`,` /**`,` * @param {import('arkormx').SchemaBuilder} schema`,` * @returns {Promise<void>}`,` */`,` async up (schema) {`,` }`,``,` /**`,` * @param {import('arkormx').SchemaBuilder} schema`,` * @returns {Promise<void>}`,` */`,` async down (schema) {`,` }`,`}`,``].join(`
9
- `):[`import { Migration, SchemaBuilder } from 'arkormx'`,``,`export default class ${e} extends Migration {`,` public async up (schema: SchemaBuilder): Promise<void> {`,` }`,``,` public async down (schema: SchemaBuilder): Promise<void> {`,` }`,`}`,``].join(`
10
- `),H=(e,t={})=>{let n=z(new Date),r=B(e),i=L(e),a=t.extension??`ts`,o=t.directory??v(process.cwd(),`database`,`migrations`),s=`${n}_${r}.${a}`,c=v(o,s),l=V(i,a);if(t.write??!0){if(p(o)||m(o,{recursive:!0}),p(c))throw new T(`Migration file already exists: ${c}`);_(c,l)}return{fileName:s,filePath:c,className:i,content:l}},U=async(e,t=`up`)=>{let n=e instanceof E?e:new e,r=new re;return t===`up`?await n.up(r):await n.down(r),r.getOperations()},W=async(e,t={})=>{let n=t.schemaPath??v(process.cwd(),`prisma`,`schema.prisma`);if(!p(n))throw new T(`Prisma schema file not found: ${n}`);let r=h(n,`utf-8`),i=await U(e,`up`),a=ce(r,i);return(t.write??!0)&&_(n,a),{schema:a,schemaPath:n,operations:i}},G=()=>{let e=s.dirname(te(import.meta.url));for(;;){let n=s.join(e,`package.json`),r=s.join(e,`stubs`);if(t(n)&&t(r))return r;let i=s.dirname(e);if(i===e)break;e=i}return s.join(process.cwd(),`stubs`)},K={paths:{stubs:G(),seeders:s.join(process.cwd(),`database`,`seeders`),models:s.join(process.cwd(),`src`,`models`),migrations:s.join(process.cwd(),`database`,`migrations`),factories:s.join(process.cwd(),`database`,`factories`),buildOutput:s.join(process.cwd(),`dist`)},outputExt:`ts`},q={...K,paths:{...K.paths??{}}};let J=!1,Y;const le=e=>{let t=K.paths??{},n=q.paths??{},r=Object.entries(e??{}).reduce((e,[t,n])=>(typeof n==`string`&&n.trim().length>0&&(e[t]=n),e),{});return{...t,...n,...r}},X=e=>e?q[e]:q,ue=(e,t={})=>{let n={...q,prisma:e,paths:le(t.paths)};t.pagination!==void 0&&(n.pagination=t.pagination),t.outputExt!==void 0&&(n.outputExt=t.outputExt),Object.assign(q,{...n}),n.pagination?.urlDriver},Z=e=>{let t=e?.default??e;!t||typeof t!=`object`||!t.prisma||(ue(t.prisma,{pagination:t.pagination,paths:t.paths,outputExt:t.outputExt}),J=!0)},de=e=>import(`${ne(e).href}?arkorm_runtime=${Date.now()}`),fe=()=>{let e=f(import.meta.url),n=[s.join(process.cwd(),`arkormx.config.cjs`)];for(let r of n)if(t(r))try{return Z(e(r)),!0}catch{continue}return!1},pe=async()=>{if(!J){if(Y)return await Y;fe()||(Y=(async()=>{let e=[s.join(process.cwd(),`arkormx.config.js`),s.join(process.cwd(),`arkormx.config.ts`)];for(let n of e)if(t(n))try{Z(await de(n));return}catch{continue}J=!0})(),await Y)}},Q=()=>G();pe();var me=class{command;config={};constructor(){this.config=X()}getConfig=X;ensureDirectory(e){let r=c(e);t(r)||n(r,{recursive:!0})}formatPathForLog(e){let t=d(process.cwd(),e);return t?t.startsWith(`..`)?e:t:`.`}splitLogger(e,t){return t=t.includes(process.cwd())?this.formatPathForLog(t):t,x.twoColumnDetail(e+` `,` `+t,!1).join(``)}hasTypeScriptInstalled(){try{return f(import.meta.url).resolve(`typescript`,{paths:[process.cwd()]}),!0}catch{return!1}}resolveOutputExt(){let e=this.getConfig(`outputExt`)===`js`?`js`:`ts`;return e===`ts`&&!this.hasTypeScriptInstalled()?`js`:e}stripKnownSourceExtension(e){return e.replace(/\.(ts|tsx|mts|cts|js|mjs|cjs)$/i,``)}resolveRuntimeDirectoryPath(e){if(t(e))return e;let{buildOutput:n}=this.getConfig(`paths`)||{};if(typeof n!=`string`||n.trim().length===0)return e;let r=d(process.cwd(),e);if(!r||r.startsWith(`..`))return e;let i=u(n,r);return t(i)?i:e}resolveRuntimeScriptPath(e){let n=l(e).toLowerCase(),r=n===`.ts`||n===`.mts`||n===`.cts`,i=[];if(r){let t=e.slice(0,-n.length);i.push(`${t}.js`,`${t}.cjs`,`${t}.mjs`)}let{buildOutput:a}=this.getConfig(`paths`)??{};if(typeof a==`string`&&a.trim().length>0){let t=d(process.cwd(),e);if(t&&!t.startsWith(`..`)){let e=u(a,t),n=l(e).toLowerCase();if(n===`.ts`||n===`.mts`||n===`.cts`){let t=e.slice(0,-n.length);i.push(`${t}.js`,`${t}.cjs`,`${t}.mjs`)}else i.push(e)}}return i.find(e=>t(e))||e}generateFile(e,n,i,s){t(n)&&!s?.force?(this.command.error(`Error: ${this.formatPathForLog(n)} already exists.`),process.exit(1)):t(n)&&s?.force&&a(n);let c=r(e,`utf-8`);for(let[e,t]of Object.entries(i))c=c.replace(RegExp(`{{${e}}}`,`g`),t);return this.ensureDirectory(n),o(n,c),n}resolveConfigPath(e,t){let{[e]:n}=this.getConfig(`paths`)??{};return typeof n==`string`&&n.trim().length>0?n:t}resolveStubPath(e){return u(this.resolveConfigPath(`stubs`,Q()),e)}makeFactory(e,t={}){let n=b(e.replace(/Factory$/,``)).pascal(),r=`${n}Factory`,i=t.modelName?b(t.modelName).pascal():n,a=this.resolveOutputExt(),o=u(this.resolveConfigPath(`factories`,u(process.cwd(),`database`,`factories`)),`${r}.${a}`),s=u(this.resolveConfigPath(`models`,u(process.cwd(),`src`,`models`)),`${i}.${a}`),l=t.modelImportPath??`./${this.stripKnownSourceExtension(d(c(o),s).replace(/\\/g,`/`))}${a===`js`?`.js`:``}`,f=this.resolveStubPath(a===`js`?`factory.js.stub`:`factory.stub`);return{name:r,path:this.generateFile(f,o,{FactoryName:r,ModelName:i.toString(),ModelImportPath:l.startsWith(`.`)?l:`./${l}`},t)}}makeSeeder(e,t={}){let n=`${b(e.replace(/Seeder$/,``)).pascal()}Seeder`,r=this.resolveOutputExt(),i=u(this.resolveConfigPath(`seeders`,u(process.cwd(),`database`,`seeders`)),`${n}.${r}`),a=this.resolveStubPath(r===`js`?`seeder.js.stub`:`seeder.stub`);return{name:n,path:this.generateFile(a,i,{SeederName:n},t)}}makeMigration(e){let t=H(e,{directory:this.resolveConfigPath(`migrations`,u(process.cwd(),`database`,`migrations`)),extension:this.resolveOutputExt()});return{name:t.className,path:t.filePath}}makeModel(e,t={}){let n=b(e.replace(/Model$/,``)).pascal().toString(),r=`${n}`,i=b(n).camel().plural().toString(),a=this.resolveOutputExt(),o=u(this.resolveConfigPath(`models`,u(process.cwd(),`src`,`models`)),`${r}.${a}`),s=t.all||t.factory,l=t.all||t.seeder,f=t.all||t.migration,p=`${n}Factory`,m=u(this.resolveConfigPath(`factories`,u(process.cwd(),`database`,`factories`)),`${p}.${a}`),h=`./${d(c(o),m).replace(/\\/g,`/`).replace(/\.(ts|tsx|mts|cts|js|mjs|cjs)$/i,``)}${a===`js`?`.js`:``}`,g=this.resolveStubPath(a===`js`?`model.js.stub`:`model.stub`),_=this.generateFile(g,o,{ModelName:r,DelegateName:i,FactoryImport:s?`import { ${p} } from '${h}'\n`:``,FactoryLink:s?a===`js`?`\n static factoryClass = ${p}`:`\n protected static override factoryClass = ${p}`:``},t),v=this.ensurePrismaModelEntry(r,i),y={model:{name:r,path:_},prisma:v,factory:void 0,seeder:void 0,migration:void 0};return s&&(y.factory=this.makeFactory(n,{force:t.force,modelName:r,modelImportPath:`./${d(c(m),o).replace(/\\/g,`/`).replace(/\.(ts|tsx|mts|cts|js|mjs|cjs)$/i,``)}${a===`js`?`.js`:``}`})),l&&(y.seeder=this.makeSeeder(n,{force:t.force})),f&&(y.migration=this.makeMigration(`create ${i} table`)),y}ensurePrismaModelEntry(e,n){let i=u(process.cwd(),`prisma`,`schema.prisma`);if(!t(i))return{path:i,updated:!1};let a=r(i,`utf-8`),s=P(a,n),c=RegExp(`model\\s+${e}\\s*\\{`,`m`).test(a);return s||c?{path:i,updated:!1}:(o(i,F(a,{type:`createTable`,table:n,columns:[{name:`id`,type:`id`,primary:!0}]})),{path:i,updated:!0})}prismaTypeToTs(e){return e===`Int`||e===`Float`||e===`Decimal`?`number`:e===`BigInt`?`bigint`:e===`String`?`string`:e===`Boolean`?`boolean`:e===`DateTime`?`Date`:e===`Json`?`Record<string, unknown>`:e===`Bytes`?`Buffer`:`unknown`}parsePrismaModels(e){let t=[],n=/model\s+(\w+)\s*\{([\s\S]*?)\n\}/g,r=new Set([`Int`,`Float`,`Decimal`,`BigInt`,`String`,`Boolean`,`DateTime`,`Json`,`Bytes`]);for(let i of e.matchAll(n)){let e=i[1],n=i[2],a=n.match(/@@map\("([^"]+)"\)/)?.[1]??`${e.charAt(0).toLowerCase()}${e.slice(1)}s`,o=[];n.split(`
11
- `).forEach(e=>{let t=e.trim();if(!t||t.startsWith(`@@`)||t.startsWith(`//`))return;let n=t.match(/^(\w+)\s+([A-Za-z]+)(\?)?\b/);if(!n)return;let i=n[2];r.has(i)&&o.push({name:n[1],type:this.prismaTypeToTs(i),optional:!!n[3]})}),t.push({name:e,table:a,fields:o})}return t}syncModelDeclarations(e,t){let n=e.split(`
12
- `),r=n.findIndex(e=>/export\s+class\s+\w+\s+extends\s+Model<.+>\s*\{/.test(e));if(r<0)return{content:e,updated:!1};let i=-1,a=0;for(let e=r;e<n.length;e+=1){let t=n[e];if(a+=(t.match(/\{/g)||[]).length,a-=(t.match(/\}/g)||[]).length,a===0){i=e;break}}if(i<0)return{content:e,updated:!1};let o=n.slice(r+1,i).filter(e=>!/^\s*declare\s+\w+\??:\s*[^\n]+$/.test(e)),s=[...t.map(e=>` ${e}`),...o],c=[...n.slice(0,r+1),...s,...n.slice(i)].join(`
13
- `);return{content:c,updated:c!==e}}syncModelsFromPrisma(e={}){let n=e.schemaPath??u(process.cwd(),`prisma`,`schema.prisma`),a=e.modelsDir??this.resolveConfigPath(`models`,u(process.cwd(),`src`,`models`));if(!t(n))throw Error(`Prisma schema file not found: ${n}`);if(!t(a))throw Error(`Models directory not found: ${a}`);let s=r(n,`utf-8`),c=this.parsePrismaModels(s),l=i(a).filter(e=>e.endsWith(`.ts`)),d=[],f=[];return l.forEach(e=>{let t=u(a,e),n=r(t,`utf-8`),i=n.match(/export\s+class\s+(\w+)\s+extends\s+Model<'([^']+)'>/);if(!i){f.push(t);return}let s=i[1],l=i[2],p=c.find(e=>e.table===l)??c.find(e=>e.name===s);if(!p||p.fields.length===0){f.push(t);return}let m=p.fields.map(e=>`declare ${e.name}${e.optional?`?`:``}: ${e.type}`),h=this.syncModelDeclarations(n,m);if(!h.updated){f.push(t);return}o(t,h.content),d.push(t)}),{schemaPath:n,modelsDir:a,total:l.length,updated:d,skipped:f}}},$=class extends S{signature=`init
2
+ import { copyFileSync, existsSync, mkdirSync, readFileSync, readdirSync, rmSync, writeFileSync } from "fs";
3
+ import path, { dirname, extname, join, relative } from "path";
4
+ import { createRequire } from "module";
5
+ import { existsSync as existsSync$1, mkdirSync as mkdirSync$1, readFileSync as readFileSync$1, readdirSync as readdirSync$1, writeFileSync as writeFileSync$1 } from "node:fs";
6
+ import { join as join$1, resolve } from "node:path";
7
+ import { spawnSync } from "node:child_process";
8
+ import { str } from "@h3ravel/support";
9
+ import { fileURLToPath, pathToFileURL } from "url";
10
+ import { Logger } from "@h3ravel/shared";
11
+ import { Command, Kernel } from "@h3ravel/musket";
12
+ import { pathToFileURL as pathToFileURL$1 } from "node:url";
13
+
14
+ //#region src/Exceptions/ArkormException.ts
15
+ /**
16
+ * The ArkormException class is a custom error type for handling
17
+ * exceptions specific to the Arkormˣ.
18
+ *
19
+ * @author Legacy (3m1n3nc3)
20
+ * @since 0.1.0
21
+ */
22
+ var ArkormException = class extends Error {
23
+ constructor(message) {
24
+ super(message);
25
+ this.name = "ArkormException";
26
+ }
27
+ };
28
+
29
+ //#endregion
30
+ //#region src/database/Migration.ts
31
+ /**
32
+ * The Migration class serves as a base for defining database migrations, requiring
33
+ * the implementation of `up` and `down` methods to specify the changes to be
34
+ * applied or reverted in the database schema.
35
+ *
36
+ * @author Legacy (3m1n3nc3)
37
+ * @since 0.1.0
38
+ */
39
+ var Migration = class {};
40
+
41
+ //#endregion
42
+ //#region src/database/TableBuilder.ts
43
+ /**
44
+ * The TableBuilder class provides a fluent interface for defining
45
+ * the structure of a database table in a migration, including columns to add or drop.
46
+ *
47
+ * @author Legacy (3m1n3nc3)
48
+ * @since 0.1.0
49
+ */
50
+ var TableBuilder = class {
51
+ columns = [];
52
+ dropColumnNames = [];
53
+ indexes = [];
54
+ latestColumnName;
55
+ /**
56
+ * Defines a primary key column in the table.
57
+ *
58
+ * @param columnNameOrOptions
59
+ * @param options
60
+ * @returns
61
+ */
62
+ primary(columnNameOrOptions, options) {
63
+ const config = typeof columnNameOrOptions === "string" ? {
64
+ columnName: columnNameOrOptions,
65
+ ...options ?? {}
66
+ } : columnNameOrOptions ?? {};
67
+ const column = this.resolveColumn(config.columnName);
68
+ column.primary = true;
69
+ if (typeof config.autoIncrement === "boolean") column.autoIncrement = config.autoIncrement;
70
+ if (Object.prototype.hasOwnProperty.call(config, "default")) column.default = config.default;
71
+ return this;
72
+ }
73
+ /**
74
+ * Defines an auto-incrementing primary key column.
75
+ *
76
+ * @param name The name of the primary key column.
77
+ * @default 'id'
78
+ * @returns The current TableBuilder instance for chaining.
79
+ */
80
+ id(name = "id", type = "id") {
81
+ return this.column(name, type, { primary: true });
82
+ }
83
+ /**
84
+ * Defines a UUID column in the table.
85
+ *
86
+ * @param name The name of the UUID column.
87
+ * @param options Additional options for the UUID column.
88
+ * @returns The current TableBuilder instance for chaining.
89
+ */
90
+ uuid(name, options = {}) {
91
+ return this.column(name, "uuid", options);
92
+ }
93
+ /**
94
+ * Defines a string column in the table.
95
+ *
96
+ * @param name The name of the string column.
97
+ * @param options Additional options for the string column.
98
+ * @returns The current TableBuilder instance for chaining.
99
+ */
100
+ string(name, options = {}) {
101
+ return this.column(name, "string", options);
102
+ }
103
+ /**
104
+ * Defines a text column in the table.
105
+ *
106
+ * @param name The name of the text column.
107
+ * @param options Additional options for the text column.
108
+ * @returns The current TableBuilder instance for chaining.
109
+ */
110
+ text(name, options = {}) {
111
+ return this.column(name, "text", options);
112
+ }
113
+ /**
114
+ * Defines an integer column in the table.
115
+ *
116
+ * @param name The name of the integer column.
117
+ * @param options Additional options for the integer column.
118
+ * @returns
119
+ */
120
+ integer(name, options = {}) {
121
+ return this.column(name, "integer", options);
122
+ }
123
+ /**
124
+ * Defines a big integer column in the table.
125
+ *
126
+ * @param name The name of the big integer column.
127
+ * @param options Additional options for the big integer column.
128
+ * @returns
129
+ */
130
+ bigInteger(name, options = {}) {
131
+ return this.column(name, "bigInteger", options);
132
+ }
133
+ /**
134
+ * Defines a float column in the table.
135
+ *
136
+ * @param name The name of the float column.
137
+ * @param options Additional options for the float column.
138
+ * @returns
139
+ */
140
+ float(name, options = {}) {
141
+ return this.column(name, "float", options);
142
+ }
143
+ /**
144
+ * Defines a boolean column in the table.
145
+ *
146
+ * @param name The name of the boolean column.
147
+ * @param options Additional options for the boolean column.
148
+ * @returns
149
+ */
150
+ boolean(name, options = {}) {
151
+ return this.column(name, "boolean", options);
152
+ }
153
+ /**
154
+ * Defines a JSON column in the table.
155
+ *
156
+ * @param name The name of the JSON column.
157
+ * @param options Additional options for the JSON column.
158
+ * @returns
159
+ */
160
+ json(name, options = {}) {
161
+ return this.column(name, "json", options);
162
+ }
163
+ /**
164
+ * Defines a date column in the table.
165
+ *
166
+ * @param name The name of the date column.
167
+ * @param options Additional options for the date column.
168
+ * @returns
169
+ */
170
+ date(name, options = {}) {
171
+ return this.column(name, "date", options);
172
+ }
173
+ /**
174
+ * Defines colonns for a polymorphic relationship in the table.
175
+ *
176
+ * @param name The base name for the polymorphic relationship columns.
177
+ * @returns
178
+ */
179
+ morphs(name, nullable = false) {
180
+ this.string(`${name}Type`, { nullable });
181
+ this.integer(`${name}Id`, { nullable });
182
+ return this;
183
+ }
184
+ /**
185
+ * Defines nullable columns for a polymorphic relationship in the table.
186
+ *
187
+ * @param name The base name for the polymorphic relationship columns.
188
+ * @returns
189
+ */
190
+ nullableMorphs(name) {
191
+ return this.morphs(name, true);
192
+ }
193
+ /**
194
+ * Defines a timestamp column in the table.
195
+ *
196
+ * @param name The name of the timestamp column.
197
+ * @param options Additional options for the timestamp column.
198
+ * @returns
199
+ */
200
+ timestamp(name, options = {}) {
201
+ return this.column(name, "timestamp", options);
202
+ }
203
+ /**
204
+ * Defines both createdAt and updatedAt timestamp columns in the table.
205
+ *
206
+ * @returns
207
+ */
208
+ timestamps() {
209
+ this.timestamp("createdAt", { nullable: false });
210
+ this.timestamp("updatedAt", { nullable: false });
211
+ return this;
212
+ }
213
+ /**
214
+ * Defines a soft delete timestamp column in the table.
215
+ *
216
+ * @param column The name of the soft delete column.
217
+ * @returns
218
+ */
219
+ softDeletes(column = "deletedAt") {
220
+ this.timestamp(column, { nullable: true });
221
+ return this;
222
+ }
223
+ /**
224
+ * Defines a column to be dropped from the table in an alterTable operation.
225
+ *
226
+ * @param name The name of the column to drop.
227
+ * @returns
228
+ */
229
+ dropColumn(name) {
230
+ this.dropColumnNames.push(name);
231
+ return this;
232
+ }
233
+ /**
234
+ * Marks a column as nullable.
235
+ *
236
+ * @param columnName Optional explicit column name. When omitted, applies to the latest defined column.
237
+ * @returns The current TableBuilder instance for chaining.
238
+ */
239
+ nullable(columnName) {
240
+ const column = this.resolveColumn(columnName);
241
+ column.nullable = true;
242
+ return this;
243
+ }
244
+ /**
245
+ * Sets the column position to appear after another column when possible.
246
+ *
247
+ * @param referenceColumn The column that the target column should be placed after.
248
+ * @param columnName Optional explicit target column name. When omitted, applies to the latest defined column.
249
+ * @returns The current TableBuilder instance for chaining.
250
+ */
251
+ after(referenceColumn, columnName) {
252
+ const column = this.resolveColumn(columnName);
253
+ column.after = referenceColumn;
254
+ return this;
255
+ }
256
+ /**
257
+ * Maps the column to a custom database column name.
258
+ *
259
+ * @param name The custom database column name.
260
+ * @param columnName Optional explicit target column name. When omitted, applies to the latest defined column.
261
+ * @returns The current TableBuilder instance for chaining.
262
+ */
263
+ map(name, columnName) {
264
+ const column = this.resolveColumn(columnName);
265
+ column.map = name;
266
+ return this;
267
+ }
268
+ /**
269
+ * Defines an index on one or more columns.
270
+ *
271
+ * @param columns Optional target columns. When omitted, applies to the latest defined column.
272
+ * @param name Optional index name.
273
+ * @returns The current TableBuilder instance for chaining.
274
+ */
275
+ index(columns, name) {
276
+ const columnList = Array.isArray(columns) ? columns : typeof columns === "string" ? [columns] : [this.resolveColumn().name];
277
+ this.indexes.push({
278
+ columns: [...columnList],
279
+ name
280
+ });
281
+ return this;
282
+ }
283
+ /**
284
+ * Returns a deep copy of the defined columns for the table.
285
+ *
286
+ * @returns
287
+ */
288
+ getColumns() {
289
+ return this.columns.map((column) => ({ ...column }));
290
+ }
291
+ /**
292
+ * Returns a copy of the defined column names to be dropped from the table.
293
+ *
294
+ * @returns
295
+ */
296
+ getDropColumns() {
297
+ return [...this.dropColumnNames];
298
+ }
299
+ /**
300
+ * Returns a deep copy of the defined indexes for the table.
301
+ *
302
+ * @returns
303
+ */
304
+ getIndexes() {
305
+ return this.indexes.map((index) => ({
306
+ ...index,
307
+ columns: [...index.columns]
308
+ }));
309
+ }
310
+ /**
311
+ * Defines a column in the table with the given name.
312
+ *
313
+ * @param name The name of the column.
314
+ * @param type The type of the column.
315
+ * @param options Additional options for the column.
316
+ * @returns
317
+ */
318
+ column(name, type, options) {
319
+ this.columns.push({
320
+ name,
321
+ type,
322
+ map: options.map,
323
+ nullable: options.nullable,
324
+ unique: options.unique,
325
+ primary: options.primary,
326
+ autoIncrement: options.autoIncrement,
327
+ after: options.after,
328
+ default: options.default
329
+ });
330
+ this.latestColumnName = name;
331
+ return this;
332
+ }
333
+ /**
334
+ * Resolve a target column by name or fallback to the latest defined column.
335
+ *
336
+ * @param columnName
337
+ * @returns
338
+ */
339
+ resolveColumn(columnName) {
340
+ const targetName = columnName ?? this.latestColumnName;
341
+ if (!targetName) throw new Error("No column available for this operation.");
342
+ const column = this.columns.find((item) => item.name === targetName);
343
+ if (!column) throw new Error(`Column [${targetName}] was not found in the table definition.`);
344
+ return column;
345
+ }
346
+ };
347
+
348
+ //#endregion
349
+ //#region src/database/SchemaBuilder.ts
350
+ /**
351
+ * The SchemaBuilder class provides methods for defining the operations to be
352
+ * performed in a migration, such as creating, altering, or dropping tables.
353
+ *
354
+ * @author Legacy (3m1n3nc3)
355
+ * @since 0.1.0
356
+ */
357
+ var SchemaBuilder = class {
358
+ operations = [];
359
+ /**
360
+ * Defines a new table to be created in the migration.
361
+ *
362
+ * @param table The name of the table to create.
363
+ * @param callback A callback function to define the table's columns and structure.
364
+ * @returns The current SchemaBuilder instance for chaining.
365
+ */
366
+ createTable(table, callback) {
367
+ const builder = new TableBuilder();
368
+ callback(builder);
369
+ this.operations.push({
370
+ type: "createTable",
371
+ table,
372
+ columns: builder.getColumns(),
373
+ indexes: builder.getIndexes()
374
+ });
375
+ return this;
376
+ }
377
+ /**
378
+ * Defines alterations to an existing table in the migration.
379
+ *
380
+ * @param table The name of the table to alter.
381
+ * @param callback A callback function to define the alterations to the table's columns and structure.
382
+ * @returns The current SchemaBuilder instance for chaining.
383
+ */
384
+ alterTable(table, callback) {
385
+ const builder = new TableBuilder();
386
+ callback(builder);
387
+ this.operations.push({
388
+ type: "alterTable",
389
+ table,
390
+ addColumns: builder.getColumns(),
391
+ dropColumns: builder.getDropColumns(),
392
+ addIndexes: builder.getIndexes()
393
+ });
394
+ return this;
395
+ }
396
+ /**
397
+ * Defines a table to be dropped in the migration.
398
+ *
399
+ * @param table The name of the table to drop.
400
+ * @returns The current SchemaBuilder instance for chaining.
401
+ */
402
+ dropTable(table) {
403
+ this.operations.push({
404
+ type: "dropTable",
405
+ table
406
+ });
407
+ return this;
408
+ }
409
+ /**
410
+ * Returns a deep copy of the defined schema operations for the migration/
411
+ *
412
+ * @returns An array of schema operations for the migration.
413
+ */
414
+ getOperations() {
415
+ return this.operations.map((operation) => {
416
+ if (operation.type === "createTable") return {
417
+ ...operation,
418
+ columns: operation.columns.map((column) => ({ ...column })),
419
+ indexes: operation.indexes.map((index) => ({
420
+ ...index,
421
+ columns: [...index.columns]
422
+ }))
423
+ };
424
+ if (operation.type === "alterTable") return {
425
+ ...operation,
426
+ addColumns: operation.addColumns.map((column) => ({ ...column })),
427
+ dropColumns: [...operation.dropColumns],
428
+ addIndexes: operation.addIndexes.map((index) => ({
429
+ ...index,
430
+ columns: [...index.columns]
431
+ }))
432
+ };
433
+ return { ...operation };
434
+ });
435
+ }
436
+ };
437
+
438
+ //#endregion
439
+ //#region src/helpers/migrations.ts
440
+ const PRISMA_MODEL_REGEX = /model\s+(\w+)\s*\{[\s\S]*?\n\}/g;
441
+ /**
442
+ * Convert a table name to a PascalCase model name, with basic singularization.
443
+ *
444
+ * @param tableName The name of the table to convert.
445
+ * @returns The corresponding PascalCase model name.
446
+ */
447
+ const toModelName = (tableName) => {
448
+ const normalized = tableName.replace(/[^a-zA-Z0-9]+/g, " ").trim();
449
+ const parts = (normalized.endsWith("s") && normalized.length > 1 ? normalized.slice(0, -1) : normalized).split(/\s+/g).filter(Boolean);
450
+ if (parts.length === 0) return "GeneratedModel";
451
+ return parts.map((part) => `${part.charAt(0).toUpperCase()}${part.slice(1)}`).join("");
452
+ };
453
+ /**
454
+ * Escape special characters in a string for use in a regular expression.
455
+ *
456
+ * @param value
457
+ * @returns
458
+ */
459
+ const escapeRegex = (value) => value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
460
+ /**
461
+ * Convert a SchemaColumn definition to a Prisma field type string, including modifiers.
462
+ *
463
+ * @param column
464
+ * @returns
465
+ */
466
+ const resolvePrismaType = (column) => {
467
+ if (column.type === "id") return "Int";
468
+ if (column.type === "uuid") return "String";
469
+ if (column.type === "string" || column.type === "text") return "String";
470
+ if (column.type === "integer") return "Int";
471
+ if (column.type === "bigInteger") return "BigInt";
472
+ if (column.type === "float") return "Float";
473
+ if (column.type === "boolean") return "Boolean";
474
+ if (column.type === "json") return "Json";
475
+ return "DateTime";
476
+ };
477
+ /**
478
+ * Format a default value for inclusion in a Prisma schema field definition, based on its type.
479
+ *
480
+ * @param value
481
+ * @returns
482
+ */
483
+ const formatDefaultValue = (value) => {
484
+ if (value == null) return void 0;
485
+ if (typeof value === "string") return `@default("${value.replace(/"/g, "\\\"")}")`;
486
+ if (typeof value === "number" || typeof value === "bigint") return `@default(${value})`;
487
+ if (typeof value === "boolean") return `@default(${value ? "true" : "false"})`;
488
+ };
489
+ /**
490
+ * Build a single line of a Prisma model field definition based on a SchemaColumn, including type and modifiers.
491
+ *
492
+ * @param column
493
+ * @returns
494
+ */
495
+ const buildFieldLine = (column) => {
496
+ if (column.type === "id") {
497
+ const primary = column.primary === false ? "" : " @id";
498
+ const mapped = typeof column.map === "string" && column.map.trim().length > 0 ? ` @map("${column.map.replace(/"/g, "\\\"")}")` : "";
499
+ const configuredDefault = formatDefaultValue(column.default);
500
+ const shouldAutoIncrement = column.autoIncrement ?? column.primary !== false;
501
+ const defaultSuffix = configuredDefault ? ` ${configuredDefault}` : shouldAutoIncrement && primary ? " @default(autoincrement())" : "";
502
+ return ` ${column.name} Int${primary}${defaultSuffix}${mapped}`;
503
+ }
504
+ const scalar = resolvePrismaType(column);
505
+ const nullable = column.nullable ? "?" : "";
506
+ const unique = column.unique ? " @unique" : "";
507
+ const primary = column.primary ? " @id" : "";
508
+ const mapped = typeof column.map === "string" && column.map.trim().length > 0 ? ` @map("${column.map.replace(/"/g, "\\\"")}")` : "";
509
+ const defaultValue = formatDefaultValue(column.default) ?? (column.type === "uuid" && column.primary ? "@default(uuid())" : void 0);
510
+ const defaultSuffix = defaultValue ? ` ${defaultValue}` : "";
511
+ return ` ${column.name} ${scalar}${nullable}${primary}${unique}${defaultSuffix}${mapped}`;
512
+ };
513
+ /**
514
+ * Build a Prisma model-level @@index definition line.
515
+ *
516
+ * @param index
517
+ * @returns
518
+ */
519
+ const buildIndexLine = (index) => {
520
+ return ` @@index([${index.columns.join(", ")}]${typeof index.name === "string" && index.name.trim().length > 0 ? `, name: "${index.name.replace(/"/g, "\\\"")}"` : ""})`;
521
+ };
522
+ /**
523
+ * Build a Prisma model block string based on a SchemaTableCreateOperation, including
524
+ * all fields and any necessary mapping.
525
+ *
526
+ * @param operation The schema table create operation to convert.
527
+ * @returns The corresponding Prisma model block string.
528
+ */
529
+ const buildModelBlock = (operation) => {
530
+ const modelName = toModelName(operation.table);
531
+ const mapped = operation.table !== modelName.toLowerCase();
532
+ const fields = operation.columns.map(buildFieldLine);
533
+ const metadata = [...(operation.indexes ?? []).map(buildIndexLine), ...mapped ? [` @@map("${str(operation.table).snake()}")`] : []];
534
+ return `model ${modelName} {\n${(metadata.length > 0 ? [
535
+ ...fields,
536
+ "",
537
+ ...metadata
538
+ ] : fields).join("\n")}\n}`;
539
+ };
540
+ /**
541
+ * Find the Prisma model block in a schema string that corresponds to a given
542
+ * table name, using both explicit mapping and naming conventions.
543
+ *
544
+ * @param schema
545
+ * @param table
546
+ * @returns
547
+ */
548
+ const findModelBlock = (schema, table) => {
549
+ const candidates = [...schema.matchAll(PRISMA_MODEL_REGEX)];
550
+ const explicitMapRegex = new RegExp(`@@map\\("${escapeRegex(table)}"\\)`);
551
+ for (const match of candidates) {
552
+ const block = match[0];
553
+ const modelName = match[1];
554
+ const start = match.index ?? 0;
555
+ const end = start + block.length;
556
+ if (explicitMapRegex.test(block)) return {
557
+ modelName,
558
+ block,
559
+ start,
560
+ end
561
+ };
562
+ if (modelName.toLowerCase() === table.toLowerCase()) return {
563
+ modelName,
564
+ block,
565
+ start,
566
+ end
567
+ };
568
+ if (modelName.toLowerCase() === toModelName(table).toLowerCase()) return {
569
+ modelName,
570
+ block,
571
+ start,
572
+ end
573
+ };
574
+ }
575
+ return null;
576
+ };
577
+ /**
578
+ * Apply a create table operation to a Prisma schema string, adding a new model
579
+ * block for the specified table and fields.
580
+ *
581
+ * @param schema The current Prisma schema string.
582
+ * @param operation The schema table create operation to apply.
583
+ * @returns The updated Prisma schema string with the new model block.
584
+ */
585
+ const applyCreateTableOperation = (schema, operation) => {
586
+ if (findModelBlock(schema, operation.table)) throw new ArkormException(`Prisma model for table [${operation.table}] already exists.`);
587
+ const block = buildModelBlock(operation);
588
+ return `${schema.trimEnd()}\n\n${block}\n`;
589
+ };
590
+ /**
591
+ * Apply an alter table operation to a Prisma schema string, modifying the model
592
+ * block for the specified table by adding and removing fields as needed.
593
+ *
594
+ * @param schema The current Prisma schema string.
595
+ * @param operation The schema table alter operation to apply.
596
+ * @returns The updated Prisma schema string with the modified model block.
597
+ */
598
+ const applyAlterTableOperation = (schema, operation) => {
599
+ const model = findModelBlock(schema, operation.table);
600
+ if (!model) throw new ArkormException(`Prisma model for table [${operation.table}] was not found.`);
601
+ let block = model.block;
602
+ const bodyLines = block.split("\n");
603
+ operation.dropColumns.forEach((column) => {
604
+ const columnRegex = new RegExp(`^\\s*${escapeRegex(column)}\\s+`);
605
+ for (let index = 0; index < bodyLines.length; index += 1) if (columnRegex.test(bodyLines[index])) {
606
+ bodyLines.splice(index, 1);
607
+ return;
608
+ }
609
+ });
610
+ operation.addColumns.forEach((column) => {
611
+ const fieldLine = buildFieldLine(column);
612
+ const columnRegex = new RegExp(`^\\s*${escapeRegex(column.name)}\\s+`);
613
+ if (bodyLines.some((line) => columnRegex.test(line))) return;
614
+ const defaultInsertIndex = Math.max(1, bodyLines.length - 1);
615
+ const afterInsertIndex = typeof column.after === "string" && column.after.length > 0 ? bodyLines.findIndex((line) => new RegExp(`^\\s*${escapeRegex(column.after)}\\s+`).test(line)) : -1;
616
+ const insertIndex = afterInsertIndex > 0 ? Math.min(afterInsertIndex + 1, defaultInsertIndex) : defaultInsertIndex;
617
+ bodyLines.splice(insertIndex, 0, fieldLine);
618
+ });
619
+ (operation.addIndexes ?? []).forEach((index) => {
620
+ const indexLine = buildIndexLine(index);
621
+ if (bodyLines.some((line) => line.trim() === indexLine.trim())) return;
622
+ const insertIndex = Math.max(1, bodyLines.length - 1);
623
+ bodyLines.splice(insertIndex, 0, indexLine);
624
+ });
625
+ block = bodyLines.join("\n");
626
+ return `${schema.slice(0, model.start)}${block}${schema.slice(model.end)}`;
627
+ };
628
+ /**
629
+ * Apply a drop table operation to a Prisma schema string, removing the model block
630
+ * for the specified table.
631
+ */
632
+ const applyDropTableOperation = (schema, operation) => {
633
+ const model = findModelBlock(schema, operation.table);
634
+ if (!model) return schema;
635
+ const before = schema.slice(0, model.start).trimEnd();
636
+ const after = schema.slice(model.end).trimStart();
637
+ return `${before}${before && after ? "\n\n" : ""}${after}`;
638
+ };
639
+ /**
640
+ * The SchemaBuilder class provides a fluent interface for defining
641
+ * database schema operations in a migration, such as creating, altering, and
642
+ * dropping tables.
643
+ *
644
+ * @param schema The current Prisma schema string.
645
+ * @param operations The list of schema operations to apply.
646
+ * @returns The updated Prisma schema string after applying all operations.
647
+ */
648
+ const applyOperationsToPrismaSchema = (schema, operations) => {
649
+ return operations.reduce((current, operation) => {
650
+ if (operation.type === "createTable") return applyCreateTableOperation(current, operation);
651
+ if (operation.type === "alterTable") return applyAlterTableOperation(current, operation);
652
+ return applyDropTableOperation(current, operation);
653
+ }, schema);
654
+ };
655
+ /**
656
+ * Run a Prisma CLI command using npx, capturing and throwing any errors that occur.
657
+ *
658
+ * @param args The arguments to pass to the Prisma CLI command.
659
+ * @param cwd The current working directory to run the command in.
660
+ * @returns void
661
+ */
662
+ const runPrismaCommand = (args, cwd) => {
663
+ const command = spawnSync("npx", ["prisma", ...args], {
664
+ cwd,
665
+ encoding: "utf-8"
666
+ });
667
+ if (command.status === 0) return;
668
+ const errorOutput = [command.stdout, command.stderr].filter(Boolean).join("\n").trim();
669
+ throw new ArkormException(errorOutput ? `Prisma command failed: prisma ${args.join(" ")}\n${errorOutput}` : `Prisma command failed: prisma ${args.join(" ")}`);
670
+ };
671
+ /**
672
+ * Generate a new migration file with a given name and options, including
673
+ * writing the file to disk if specified.
674
+ *
675
+ * @param name
676
+ * @returns
677
+ */
678
+ const resolveMigrationClassName = (name) => {
679
+ const cleaned = name.replace(/[^a-zA-Z0-9]+/g, " ").trim();
680
+ if (!cleaned) return "GeneratedMigration";
681
+ return `${cleaned.split(/\s+/g).map((part) => `${part.charAt(0).toUpperCase()}${part.slice(1)}`).join("")}Migration`;
682
+ };
683
+ /**
684
+ * Pad a number with leading zeros to ensure it is at least two digits, for
685
+ * use in migration timestamps.
686
+ *
687
+ * @param value
688
+ * @returns
689
+ */
690
+ const pad = (value) => String(value).padStart(2, "0");
691
+ /**
692
+ * Create a timestamp string in the format YYYYMMDDHHMMSS for use in migration
693
+ * file names, based on the current date and time or a provided date.
694
+ *
695
+ * @param date
696
+ * @returns
697
+ */
698
+ const createMigrationTimestamp = (date = /* @__PURE__ */ new Date()) => {
699
+ return `${date.getFullYear()}${pad(date.getMonth() + 1)}${pad(date.getDate())}${pad(date.getHours())}${pad(date.getMinutes())}${pad(date.getSeconds())}`;
700
+ };
701
+ /**
702
+ * Convert a migration name to a slug suitable for use in a file name, by
703
+ * lowercasing and replacing non-alphanumeric characters with underscores.
704
+ *
705
+ * @param name
706
+ * @returns
707
+ */
708
+ const toMigrationFileSlug = (name) => {
709
+ return name.trim().toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_+|_+$/g, "") || "migration";
710
+ };
711
+ /**
712
+ * Build the source code for a new migration file based on a given class
713
+ * name, using a template with empty up and down methods.
714
+ *
715
+ * @param className
716
+ * @returns
717
+ */
718
+ const buildMigrationSource = (className, extension = "ts") => {
719
+ if (extension === "js") return [
720
+ "import { Migration } from 'arkormx'",
721
+ "",
722
+ `export default class ${className} extends Migration {`,
723
+ " /**",
724
+ " * @param {import('arkormx').SchemaBuilder} schema",
725
+ " * @returns {Promise<void>}",
726
+ " */",
727
+ " async up (schema) {",
728
+ " }",
729
+ "",
730
+ " /**",
731
+ " * @param {import('arkormx').SchemaBuilder} schema",
732
+ " * @returns {Promise<void>}",
733
+ " */",
734
+ " async down (schema) {",
735
+ " }",
736
+ "}",
737
+ ""
738
+ ].join("\n");
739
+ return [
740
+ "import { Migration, SchemaBuilder } from 'arkormx'",
741
+ "",
742
+ `export default class ${className} extends Migration {`,
743
+ " public async up (schema: SchemaBuilder): Promise<void> {",
744
+ " }",
745
+ "",
746
+ " public async down (schema: SchemaBuilder): Promise<void> {",
747
+ " }",
748
+ "}",
749
+ ""
750
+ ].join("\n");
751
+ };
752
+ /**
753
+ * Generate a new migration file with a given name and options, including
754
+ * writing the file to disk if specified, and return the details of the generated file.
755
+ *
756
+ * @param name
757
+ * @param options
758
+ * @returns
759
+ */
760
+ const generateMigrationFile = (name, options = {}) => {
761
+ const timestamp = createMigrationTimestamp(/* @__PURE__ */ new Date());
762
+ const fileSlug = toMigrationFileSlug(name);
763
+ const className = resolveMigrationClassName(name);
764
+ const extension = options.extension ?? "ts";
765
+ const directory = options.directory ?? join$1(process.cwd(), "database", "migrations");
766
+ const fileName = `${timestamp}_${fileSlug}.${extension}`;
767
+ const filePath = join$1(directory, fileName);
768
+ const content = buildMigrationSource(className, extension);
769
+ if (options.write ?? true) {
770
+ if (!existsSync$1(directory)) mkdirSync$1(directory, { recursive: true });
771
+ if (existsSync$1(filePath)) throw new ArkormException(`Migration file already exists: ${filePath}`);
772
+ writeFileSync$1(filePath, content);
773
+ }
774
+ return {
775
+ fileName,
776
+ filePath,
777
+ className,
778
+ content
779
+ };
780
+ };
781
+ /**
782
+ * Get the list of schema operations that would be performed by a given migration class when run in a specified direction (up or down), without actually applying them.
783
+ *
784
+ * @param migration The migration class or instance to analyze.
785
+ * @param direction The direction of the migration to plan for ('up' or 'down').
786
+ * @returns A promise that resolves to an array of schema operations that would be performed.
787
+ */
788
+ const getMigrationPlan = async (migration, direction = "up") => {
789
+ const instance = migration instanceof Migration ? migration : new migration();
790
+ const schema = new SchemaBuilder();
791
+ if (direction === "up") await instance.up(schema);
792
+ else await instance.down(schema);
793
+ return schema.getOperations();
794
+ };
795
+ /**
796
+ * Apply the schema operations defined in a migration to a Prisma schema
797
+ * file, updating the file on disk if specified, and return the updated
798
+ * schema and list of operations applied.
799
+ *
800
+ * @param migration The migration class or instance to apply.
801
+ * @param options Options for applying the migration, including schema path and write flag.
802
+ * @returns A promise that resolves to an object containing the updated schema, schema path, and list of operations applied.
803
+ */
804
+ const applyMigrationToPrismaSchema = async (migration, options = {}) => {
805
+ const schemaPath = options.schemaPath ?? join$1(process.cwd(), "prisma", "schema.prisma");
806
+ if (!existsSync$1(schemaPath)) throw new ArkormException(`Prisma schema file not found: ${schemaPath}`);
807
+ const source = readFileSync$1(schemaPath, "utf-8");
808
+ const operations = await getMigrationPlan(migration, "up");
809
+ const schema = applyOperationsToPrismaSchema(source, operations);
810
+ if (options.write ?? true) writeFileSync$1(schemaPath, schema);
811
+ return {
812
+ schema,
813
+ schemaPath,
814
+ operations
815
+ };
816
+ };
817
+
818
+ //#endregion
819
+ //#region src/helpers/runtime-config.ts
820
+ const resolveDefaultStubsPath = () => {
821
+ let current = path.dirname(fileURLToPath(import.meta.url));
822
+ while (true) {
823
+ const packageJsonPath = path.join(current, "package.json");
824
+ const stubsPath = path.join(current, "stubs");
825
+ if (existsSync(packageJsonPath) && existsSync(stubsPath)) return stubsPath;
826
+ const parent = path.dirname(current);
827
+ if (parent === current) break;
828
+ current = parent;
829
+ }
830
+ return path.join(process.cwd(), "stubs");
831
+ };
832
+ const baseConfig = {
833
+ paths: {
834
+ stubs: resolveDefaultStubsPath(),
835
+ seeders: path.join(process.cwd(), "database", "seeders"),
836
+ models: path.join(process.cwd(), "src", "models"),
837
+ migrations: path.join(process.cwd(), "database", "migrations"),
838
+ factories: path.join(process.cwd(), "database", "factories"),
839
+ buildOutput: path.join(process.cwd(), "dist")
840
+ },
841
+ outputExt: "ts"
842
+ };
843
+ const userConfig = {
844
+ ...baseConfig,
845
+ paths: { ...baseConfig.paths ?? {} }
846
+ };
847
+ let runtimeConfigLoaded = false;
848
+ let runtimeConfigLoadingPromise;
849
+ let runtimeClientResolver;
850
+ let runtimePaginationURLDriverFactory;
851
+ const mergePathConfig = (paths) => {
852
+ const defaults = baseConfig.paths ?? {};
853
+ const current = userConfig.paths ?? {};
854
+ const incoming = Object.entries(paths ?? {}).reduce((all, [key, value]) => {
855
+ if (typeof value === "string" && value.trim().length > 0) all[key] = path.isAbsolute(value) ? value : path.resolve(process.cwd(), value);
856
+ return all;
857
+ }, {});
858
+ return {
859
+ ...defaults,
860
+ ...current,
861
+ ...incoming
862
+ };
863
+ };
864
+ /**
865
+ * Get the user-provided ArkORM configuration.
866
+ *
867
+ * @returns The user-provided ArkORM configuration object.
868
+ */
869
+ const getUserConfig = (key) => {
870
+ if (key) return userConfig[key];
871
+ return userConfig;
872
+ };
873
+ /**
874
+ * Configure the ArkORM runtime with the provided Prisma client resolver and
875
+ * delegate mapping resolver.
876
+ *
877
+ * @param prisma
878
+ * @param mapping
879
+ */
880
+ const configureArkormRuntime = (prisma, options = {}) => {
881
+ const nextConfig = {
882
+ ...userConfig,
883
+ prisma,
884
+ paths: mergePathConfig(options.paths)
885
+ };
886
+ if (options.pagination !== void 0) nextConfig.pagination = options.pagination;
887
+ if (options.outputExt !== void 0) nextConfig.outputExt = options.outputExt;
888
+ Object.assign(userConfig, { ...nextConfig });
889
+ runtimeClientResolver = prisma;
890
+ runtimePaginationURLDriverFactory = nextConfig.pagination?.urlDriver;
891
+ };
892
+ /**
893
+ * Resolve and apply the ArkORM configuration from an imported module.
894
+ * This function checks for a default export and falls back to the module itself, then validates
895
+ * the configuration object and applies it to the runtime if valid.
896
+ *
897
+ * @param imported
898
+ * @returns
899
+ */
900
+ const resolveAndApplyConfig = (imported) => {
901
+ const config = imported?.default ?? imported;
902
+ if (!config || typeof config !== "object" || !config.prisma) return;
903
+ configureArkormRuntime(config.prisma, {
904
+ pagination: config.pagination,
905
+ paths: config.paths,
906
+ outputExt: config.outputExt
907
+ });
908
+ runtimeConfigLoaded = true;
909
+ };
910
+ /**
911
+ * Dynamically import a configuration file.
912
+ * A cache-busting query parameter is appended to ensure the latest version is loaded.
913
+ *
914
+ * @param configPath
915
+ * @returns A promise that resolves to the imported configuration module.
916
+ */
917
+ const importConfigFile = (configPath) => {
918
+ return import(`${pathToFileURL(configPath).href}?arkorm_runtime=${Date.now()}`);
919
+ };
920
+ const loadRuntimeConfigSync = () => {
921
+ const require = createRequire(import.meta.url);
922
+ const syncConfigPaths = [path.join(process.cwd(), "arkormx.config.cjs")];
923
+ for (const configPath of syncConfigPaths) {
924
+ if (!existsSync(configPath)) continue;
925
+ try {
926
+ resolveAndApplyConfig(require(configPath));
927
+ return true;
928
+ } catch {
929
+ continue;
930
+ }
931
+ }
932
+ return false;
933
+ };
934
+ /**
935
+ * Load the ArkORM configuration by searching for configuration files in the
936
+ * current working directory.
937
+ * @returns
938
+ */
939
+ const loadArkormConfig = async () => {
940
+ if (runtimeConfigLoaded) return;
941
+ if (runtimeConfigLoadingPromise) return await runtimeConfigLoadingPromise;
942
+ if (loadRuntimeConfigSync()) return;
943
+ runtimeConfigLoadingPromise = (async () => {
944
+ const configPaths = [path.join(process.cwd(), "arkormx.config.js"), path.join(process.cwd(), "arkormx.config.ts")];
945
+ for (const configPath of configPaths) {
946
+ if (!existsSync(configPath)) continue;
947
+ try {
948
+ resolveAndApplyConfig(await importConfigFile(configPath));
949
+ return;
950
+ } catch {
951
+ continue;
952
+ }
953
+ }
954
+ runtimeConfigLoaded = true;
955
+ })();
956
+ await runtimeConfigLoadingPromise;
957
+ };
958
+ const getDefaultStubsPath = () => {
959
+ return resolveDefaultStubsPath();
960
+ };
961
+ loadArkormConfig();
962
+
963
+ //#endregion
964
+ //#region src/cli/CliApp.ts
965
+ /**
966
+ * Main application class for the Arkormˣ CLI.
967
+ *
968
+ * @author Legacy (3m1n3nc3)
969
+ * @since 0.1.0
970
+ */
971
+ var CliApp = class {
972
+ command;
973
+ config = {};
974
+ constructor() {
975
+ this.config = getUserConfig();
976
+ }
977
+ /**
978
+ * Get the current configuration object or a specific configuration value.
979
+ *
980
+ * @param key Optional specific configuration key to retrieve
981
+ * @returns The entire configuration object or the value of the specified key
982
+ */
983
+ getConfig = getUserConfig;
984
+ /**
985
+ * Utility to ensure directory exists
986
+ *
987
+ * @param filePath
988
+ */
989
+ ensureDirectory(filePath) {
990
+ const dir = dirname(filePath);
991
+ if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
992
+ }
993
+ /**
994
+ * Convert absolute paths under current working directory into relative display paths.
995
+ *
996
+ * @param filePath
997
+ * @returns
998
+ */
999
+ formatPathForLog(filePath) {
1000
+ const relPath = relative(process.cwd(), filePath);
1001
+ if (!relPath) return ".";
1002
+ if (relPath.startsWith("..")) return filePath;
1003
+ return relPath;
1004
+ }
1005
+ /**
1006
+ * Utility to format a value for logging, converting absolute paths under current
1007
+ * working directory into relative display paths.
1008
+ *
1009
+ * @param name
1010
+ * @param value
1011
+ * @returns
1012
+ */
1013
+ splitLogger(name, value) {
1014
+ value = value.includes(process.cwd()) ? this.formatPathForLog(value) : value;
1015
+ return Logger.twoColumnDetail(name + " ", " " + value, false).join("");
1016
+ }
1017
+ hasTypeScriptInstalled() {
1018
+ try {
1019
+ createRequire(import.meta.url).resolve("typescript", { paths: [process.cwd()] });
1020
+ return true;
1021
+ } catch {
1022
+ return false;
1023
+ }
1024
+ }
1025
+ resolveOutputExt() {
1026
+ const preferred = this.getConfig("outputExt") === "js" ? "js" : "ts";
1027
+ if (preferred === "ts" && !this.hasTypeScriptInstalled()) return "js";
1028
+ return preferred;
1029
+ }
1030
+ stripKnownSourceExtension(value) {
1031
+ return value.replace(/\.(ts|tsx|mts|cts|js|mjs|cjs)$/i, "");
1032
+ }
1033
+ /**
1034
+ * Resolve a directory path to runtime output when the source path is unavailable.
1035
+ *
1036
+ * @param directoryPath
1037
+ * @returns
1038
+ */
1039
+ resolveRuntimeDirectoryPath(directoryPath) {
1040
+ if (existsSync(directoryPath)) return directoryPath;
1041
+ const { buildOutput } = this.getConfig("paths") || {};
1042
+ if (typeof buildOutput !== "string" || buildOutput.trim().length === 0) return directoryPath;
1043
+ const relativeSource = relative(process.cwd(), directoryPath);
1044
+ if (!relativeSource || relativeSource.startsWith("..")) return directoryPath;
1045
+ const mappedDirectory = join(buildOutput, relativeSource);
1046
+ return existsSync(mappedDirectory) ? mappedDirectory : directoryPath;
1047
+ }
1048
+ /**
1049
+ * Resolve a script file path for runtime execution.
1050
+ * If a .ts file is provided, tries equivalent .js/.cjs/.mjs files first.
1051
+ * Also attempts mapped paths inside paths.buildOutput preserving structure.
1052
+ *
1053
+ * @param filePath
1054
+ * @returns
1055
+ */
1056
+ resolveRuntimeScriptPath(filePath) {
1057
+ const extension = extname(filePath).toLowerCase();
1058
+ const isTsFile = extension === ".ts" || extension === ".mts" || extension === ".cts";
1059
+ const candidates = [];
1060
+ if (isTsFile) {
1061
+ const base = filePath.slice(0, -extension.length);
1062
+ candidates.push(`${base}.js`, `${base}.cjs`, `${base}.mjs`);
1063
+ }
1064
+ const { buildOutput } = this.getConfig("paths") ?? {};
1065
+ if (typeof buildOutput === "string" && buildOutput.trim().length > 0) {
1066
+ const relativeSource = relative(process.cwd(), filePath);
1067
+ if (relativeSource && !relativeSource.startsWith("..")) {
1068
+ const mappedFile = join(buildOutput, relativeSource);
1069
+ const mappedExtension = extname(mappedFile).toLowerCase();
1070
+ if (mappedExtension === ".ts" || mappedExtension === ".mts" || mappedExtension === ".cts") {
1071
+ const mappedBase = mappedFile.slice(0, -mappedExtension.length);
1072
+ candidates.push(`${mappedBase}.js`, `${mappedBase}.cjs`, `${mappedBase}.mjs`);
1073
+ } else candidates.push(mappedFile);
1074
+ }
1075
+ }
1076
+ const runtimeMatch = candidates.find((path) => existsSync(path));
1077
+ if (runtimeMatch) return runtimeMatch;
1078
+ return filePath;
1079
+ }
1080
+ /**
1081
+ * Utility to generate file from stub
1082
+ *
1083
+ * @param stubPath
1084
+ * @param outputPath
1085
+ * @param replacements
1086
+ */
1087
+ generateFile(stubPath, outputPath, replacements, options) {
1088
+ if (existsSync(outputPath) && !options?.force) {
1089
+ this.command.error(`Error: ${this.formatPathForLog(outputPath)} already exists.`);
1090
+ process.exit(1);
1091
+ } else if (existsSync(outputPath) && options?.force) rmSync(outputPath);
1092
+ let content = readFileSync(stubPath, "utf-8");
1093
+ for (const [key, value] of Object.entries(replacements)) content = content.replace(new RegExp(`{{${key}}}`, "g"), value);
1094
+ this.ensureDirectory(outputPath);
1095
+ writeFileSync(outputPath, content);
1096
+ return outputPath;
1097
+ }
1098
+ /**
1099
+ * Resolve a configuration path with a fallback default
1100
+ *
1101
+ * @param key The configuration key to resolve
1102
+ * @param fallback The fallback value if the configuration key is not set
1103
+ * @returns The resolved configuration path
1104
+ */
1105
+ resolveConfigPath(key, fallback) {
1106
+ const { [key]: configured } = this.getConfig("paths") ?? {};
1107
+ if (typeof configured === "string" && configured.trim().length > 0) return configured;
1108
+ return fallback;
1109
+ }
1110
+ /**
1111
+ * Resolve the path to a stub file based on configuration
1112
+ *
1113
+ * @param stubName
1114
+ * @returns
1115
+ */
1116
+ resolveStubPath(stubName) {
1117
+ return join(this.resolveConfigPath("stubs", getDefaultStubsPath()), stubName);
1118
+ }
1119
+ /**
1120
+ * Generate a factory file for a given model name.
1121
+ *
1122
+ * @param name
1123
+ * @param options
1124
+ * @returns
1125
+ */
1126
+ makeFactory(name, options = {}) {
1127
+ const baseName = str(name.replace(/Factory$/, "")).pascal();
1128
+ const factoryName = `${baseName}Factory`;
1129
+ const modelName = options.modelName ? str(options.modelName).pascal() : baseName;
1130
+ const outputExt = this.resolveOutputExt();
1131
+ const outputPath = join(this.resolveConfigPath("factories", join(process.cwd(), "database", "factories")), `${factoryName}.${outputExt}`);
1132
+ const modelPath = join(this.resolveConfigPath("models", join(process.cwd(), "src", "models")), `${modelName}.${outputExt}`);
1133
+ const relativeImport = options.modelImportPath ?? `./${this.stripKnownSourceExtension(relative(dirname(outputPath), modelPath).replace(/\\/g, "/"))}${outputExt === "js" ? ".js" : ""}`;
1134
+ const stubPath = this.resolveStubPath(outputExt === "js" ? "factory.js.stub" : "factory.stub");
1135
+ return {
1136
+ name: factoryName,
1137
+ path: this.generateFile(stubPath, outputPath, {
1138
+ FactoryName: factoryName,
1139
+ ModelName: modelName.toString(),
1140
+ ModelImportPath: relativeImport.startsWith(".") ? relativeImport : `./${relativeImport}`
1141
+ }, options)
1142
+ };
1143
+ }
1144
+ /**
1145
+ * Generate a seeder file for a given name.
1146
+ *
1147
+ * @param name
1148
+ * @param options
1149
+ * @returns
1150
+ */
1151
+ makeSeeder(name, options = {}) {
1152
+ const seederName = `${str(name.replace(/Seeder$/, "")).pascal()}Seeder`;
1153
+ const outputExt = this.resolveOutputExt();
1154
+ const outputPath = join(this.resolveConfigPath("seeders", join(process.cwd(), "database", "seeders")), `${seederName}.${outputExt}`);
1155
+ const stubPath = this.resolveStubPath(outputExt === "js" ? "seeder.js.stub" : "seeder.stub");
1156
+ return {
1157
+ name: seederName,
1158
+ path: this.generateFile(stubPath, outputPath, { SeederName: seederName }, options)
1159
+ };
1160
+ }
1161
+ /**
1162
+ * Generate a migration file for a given name.
1163
+ *
1164
+ * @param name The name of the migration.
1165
+ * @returns An object containing the name and path of the generated migration file.
1166
+ */
1167
+ makeMigration(name) {
1168
+ const generated = generateMigrationFile(name, {
1169
+ directory: this.resolveConfigPath("migrations", join(process.cwd(), "database", "migrations")),
1170
+ extension: this.resolveOutputExt()
1171
+ });
1172
+ return {
1173
+ name: generated.className,
1174
+ path: generated.filePath
1175
+ };
1176
+ }
1177
+ /**
1178
+ * Generate a model file along with optional factory, seeder, and migration files.
1179
+ *
1180
+ * @param name
1181
+ * @param options
1182
+ * @returns
1183
+ */
1184
+ makeModel(name, options = {}) {
1185
+ const baseName = str(name.replace(/Model$/, "")).pascal().toString();
1186
+ const modelName = `${baseName}`;
1187
+ const delegateName = str(baseName).camel().plural().toString();
1188
+ const outputExt = this.resolveOutputExt();
1189
+ const outputPath = join(this.resolveConfigPath("models", join(process.cwd(), "src", "models")), `${modelName}.${outputExt}`);
1190
+ const shouldBuildFactory = options.all || options.factory;
1191
+ const shouldBuildSeeder = options.all || options.seeder;
1192
+ const shouldBuildMigration = options.all || options.migration;
1193
+ const factoryName = `${baseName}Factory`;
1194
+ const factoryPath = join(this.resolveConfigPath("factories", join(process.cwd(), "database", "factories")), `${factoryName}.${outputExt}`);
1195
+ const factoryImportPath = `./${relative(dirname(outputPath), factoryPath).replace(/\\/g, "/").replace(/\.(ts|tsx|mts|cts|js|mjs|cjs)$/i, "")}${outputExt === "js" ? ".js" : ""}`;
1196
+ const stubPath = this.resolveStubPath(outputExt === "js" ? "model.js.stub" : "model.stub");
1197
+ const modelPath = this.generateFile(stubPath, outputPath, {
1198
+ ModelName: modelName,
1199
+ DelegateName: delegateName,
1200
+ FactoryImport: shouldBuildFactory ? `import { ${factoryName} } from '${factoryImportPath}'\n` : "",
1201
+ FactoryLink: shouldBuildFactory ? outputExt === "js" ? `\n static factoryClass = ${factoryName}` : `\n protected static override factoryClass = ${factoryName}` : ""
1202
+ }, options);
1203
+ const prisma = this.ensurePrismaModelEntry(modelName, delegateName);
1204
+ const created = {
1205
+ model: {
1206
+ name: modelName,
1207
+ path: modelPath
1208
+ },
1209
+ prisma,
1210
+ factory: void 0,
1211
+ seeder: void 0,
1212
+ migration: void 0
1213
+ };
1214
+ if (shouldBuildFactory) created.factory = this.makeFactory(baseName, {
1215
+ force: options.force,
1216
+ modelName,
1217
+ modelImportPath: `./${relative(dirname(factoryPath), outputPath).replace(/\\/g, "/").replace(/\.(ts|tsx|mts|cts|js|mjs|cjs)$/i, "")}${outputExt === "js" ? ".js" : ""}`
1218
+ });
1219
+ if (shouldBuildSeeder) created.seeder = this.makeSeeder(baseName, { force: options.force });
1220
+ if (shouldBuildMigration) created.migration = this.makeMigration(`create ${delegateName} table`);
1221
+ return created;
1222
+ }
1223
+ /**
1224
+ * Ensure that the Prisma schema has a model entry for the given model
1225
+ * and delegate names.
1226
+ * If the entry does not exist, it will be created with a default `id` field.
1227
+ *
1228
+ * @param modelName The name of the model to ensure in the Prisma schema.
1229
+ * @param delegateName The name of the delegate (table) to ensure in the Prisma schema.
1230
+ */
1231
+ ensurePrismaModelEntry(modelName, delegateName) {
1232
+ const schemaPath = join(process.cwd(), "prisma", "schema.prisma");
1233
+ if (!existsSync(schemaPath)) return {
1234
+ path: schemaPath,
1235
+ updated: false
1236
+ };
1237
+ const source = readFileSync(schemaPath, "utf-8");
1238
+ const existingByTable = findModelBlock(source, delegateName);
1239
+ const existingByName = new RegExp(`model\\s+${modelName}\\s*\\{`, "m").test(source);
1240
+ if (existingByTable || existingByName) return {
1241
+ path: schemaPath,
1242
+ updated: false
1243
+ };
1244
+ writeFileSync(schemaPath, applyCreateTableOperation(source, {
1245
+ type: "createTable",
1246
+ table: delegateName,
1247
+ columns: [{
1248
+ name: "id",
1249
+ type: "id",
1250
+ primary: true
1251
+ }]
1252
+ }));
1253
+ return {
1254
+ path: schemaPath,
1255
+ updated: true
1256
+ };
1257
+ }
1258
+ /**
1259
+ * Convert a Prisma scalar type to its corresponding TypeScript type.
1260
+ *
1261
+ * @param value The Prisma scalar type.
1262
+ * @returns The corresponding TypeScript type.
1263
+ */
1264
+ prismaTypeToTs(value) {
1265
+ if (value === "Int" || value === "Float" || value === "Decimal") return "number";
1266
+ if (value === "BigInt") return "bigint";
1267
+ if (value === "String") return "string";
1268
+ if (value === "Boolean") return "boolean";
1269
+ if (value === "DateTime") return "Date";
1270
+ if (value === "Json") return "Record<string, unknown>";
1271
+ if (value === "Bytes") return "Buffer";
1272
+ return "unknown";
1273
+ }
1274
+ /**
1275
+ * Parse the Prisma schema to extract model definitions and their fields, focusing
1276
+ * on scalar types.
1277
+ *
1278
+ * @param schema The Prisma schema as a string.
1279
+ * @returns An array of model definitions with their fields.
1280
+ */
1281
+ parsePrismaModels(schema) {
1282
+ const models = [];
1283
+ const modelRegex = /model\s+(\w+)\s*\{([\s\S]*?)\n\}/g;
1284
+ const scalarTypes = new Set([
1285
+ "Int",
1286
+ "Float",
1287
+ "Decimal",
1288
+ "BigInt",
1289
+ "String",
1290
+ "Boolean",
1291
+ "DateTime",
1292
+ "Json",
1293
+ "Bytes"
1294
+ ]);
1295
+ for (const match of schema.matchAll(modelRegex)) {
1296
+ const name = match[1];
1297
+ const body = match[2];
1298
+ const table = body.match(/@@map\("([^"]+)"\)/)?.[1] ?? `${name.charAt(0).toLowerCase()}${name.slice(1)}s`;
1299
+ const fields = [];
1300
+ body.split("\n").forEach((rawLine) => {
1301
+ const line = rawLine.trim();
1302
+ if (!line || line.startsWith("@@") || line.startsWith("//")) return;
1303
+ const fieldMatch = line.match(/^(\w+)\s+([A-Za-z]+)(\?)?\b/);
1304
+ if (!fieldMatch) return;
1305
+ const fieldType = fieldMatch[2];
1306
+ if (!scalarTypes.has(fieldType)) return;
1307
+ fields.push({
1308
+ name: fieldMatch[1],
1309
+ type: this.prismaTypeToTs(fieldType),
1310
+ optional: Boolean(fieldMatch[3])
1311
+ });
1312
+ });
1313
+ models.push({
1314
+ name,
1315
+ table,
1316
+ fields
1317
+ });
1318
+ }
1319
+ return models;
1320
+ }
1321
+ /**
1322
+ * Sync model attribute declarations in a model file based on the
1323
+ * provided declarations.
1324
+ * This method takes the source code of a model file and a list of
1325
+ * attribute declarations,
1326
+ *
1327
+ * @param modelSource The source code of the model file.
1328
+ * @param declarations A list of attribute declarations to sync.
1329
+ * @returns An object containing the updated content and a flag indicating if it was updated.
1330
+ */
1331
+ syncModelDeclarations(modelSource, declarations) {
1332
+ const lines = modelSource.split("\n");
1333
+ const classIndex = lines.findIndex((line) => /export\s+class\s+\w+\s+extends\s+Model<.+>\s*\{/.test(line));
1334
+ if (classIndex < 0) return {
1335
+ content: modelSource,
1336
+ updated: false
1337
+ };
1338
+ let classEndIndex = -1;
1339
+ let depth = 0;
1340
+ for (let index = classIndex; index < lines.length; index += 1) {
1341
+ const line = lines[index];
1342
+ depth += (line.match(/\{/g) || []).length;
1343
+ depth -= (line.match(/\}/g) || []).length;
1344
+ if (depth === 0) {
1345
+ classEndIndex = index;
1346
+ break;
1347
+ }
1348
+ }
1349
+ if (classEndIndex < 0) return {
1350
+ content: modelSource,
1351
+ updated: false
1352
+ };
1353
+ const withoutDeclares = lines.slice(classIndex + 1, classEndIndex).filter((line) => !/^\s*declare\s+\w+\??:\s*[^\n]+$/.test(line));
1354
+ const rebuiltClass = [...declarations.map((declaration) => ` ${declaration}`), ...withoutDeclares];
1355
+ const content = [
1356
+ ...lines.slice(0, classIndex + 1),
1357
+ ...rebuiltClass,
1358
+ ...lines.slice(classEndIndex)
1359
+ ].join("\n");
1360
+ return {
1361
+ content,
1362
+ updated: content !== modelSource
1363
+ };
1364
+ }
1365
+ /**
1366
+ * Sync model attribute declarations in model files based on the Prisma schema.
1367
+ * This method reads the Prisma schema to extract model definitions and their
1368
+ * scalar fields, then updates the corresponding model files to include `declare`
1369
+ * statements for these fields. It returns an object containing the paths of the
1370
+ * schema and models, the total number of model files processed, and lists of
1371
+ * updated and skipped files.
1372
+ *
1373
+ * @param options Optional parameters to specify custom paths for the Prisma schema and models directory.
1374
+ * @returns An object with details about the synchronization process, including updated and skipped files.
1375
+ */
1376
+ syncModelsFromPrisma(options = {}) {
1377
+ const schemaPath = options.schemaPath ?? join(process.cwd(), "prisma", "schema.prisma");
1378
+ const modelsDir = options.modelsDir ?? this.resolveConfigPath("models", join(process.cwd(), "src", "models"));
1379
+ if (!existsSync(schemaPath)) throw new Error(`Prisma schema file not found: ${schemaPath}`);
1380
+ if (!existsSync(modelsDir)) throw new Error(`Models directory not found: ${modelsDir}`);
1381
+ const schema = readFileSync(schemaPath, "utf-8");
1382
+ const prismaModels = this.parsePrismaModels(schema);
1383
+ const modelFiles = readdirSync(modelsDir).filter((file) => file.endsWith(".ts"));
1384
+ const updated = [];
1385
+ const skipped = [];
1386
+ modelFiles.forEach((file) => {
1387
+ const filePath = join(modelsDir, file);
1388
+ const source = readFileSync(filePath, "utf-8");
1389
+ const classMatch = source.match(/export\s+class\s+(\w+)\s+extends\s+Model<'([^']+)'>/);
1390
+ if (!classMatch) {
1391
+ skipped.push(filePath);
1392
+ return;
1393
+ }
1394
+ const className = classMatch[1];
1395
+ const delegate = classMatch[2];
1396
+ const prismaModel = prismaModels.find((model) => model.table === delegate) ?? prismaModels.find((model) => model.name === className);
1397
+ if (!prismaModel || prismaModel.fields.length === 0) {
1398
+ skipped.push(filePath);
1399
+ return;
1400
+ }
1401
+ const declarations = prismaModel.fields.map((field) => `declare ${field.name}${field.optional ? "?" : ""}: ${field.type}`);
1402
+ const synced = this.syncModelDeclarations(source, declarations);
1403
+ if (!synced.updated) {
1404
+ skipped.push(filePath);
1405
+ return;
1406
+ }
1407
+ writeFileSync(filePath, synced.content);
1408
+ updated.push(filePath);
1409
+ });
1410
+ return {
1411
+ schemaPath,
1412
+ modelsDir,
1413
+ total: modelFiles.length,
1414
+ updated,
1415
+ skipped
1416
+ };
1417
+ }
1418
+ };
1419
+
1420
+ //#endregion
1421
+ //#region src/cli/commands/InitCommand.ts
1422
+ /**
1423
+ * The InitCommand class implements the CLI command for initializing Arkormˣ by creating
1424
+ * a default config file in the current directory.
1425
+ *
1426
+ * @author Legacy (3m1n3nc3)
1427
+ * @since 0.1.0
1428
+ */
1429
+ var InitCommand = class extends Command {
1430
+ signature = `init
14
1431
  {--force : Force overwrite if config file already exists (existing file will be backed up) }
15
- `;description=`Initialize Arkormˣ by creating a default config file in the current directory`;async handle(){this.app.command=this;let n=v(process.cwd(),`arkormx.config.js`),{stubs:i}=X(`paths`)??{},a=typeof i==`string`&&i.trim().length>0?i:Q(),s=v(a,`arkormx.config.stub`),c=v(a,`arkorm.config.stub`),l=t(s)?s:c;t(n)&&!this.option(`force`)&&(this.error(`Error: Arkormˣ has already been initialized. Use --force to reinitialize.`),process.exit(1)),this.app.ensureDirectory(n),t(n)&&this.option(`force`)&&e(n,n.replace(/\.js$/,`.backup.${Date.now()}.js`)),t(l)||(this.error(`Error: Missing config stub at ${s} (or ${c})`),process.exit(1)),o(n,r(l,`utf-8`)),this.success(`Arkormˣ initialized successfully!`)}},he=class extends S{signature=`make:factory
1432
+ `;
1433
+ description = "Initialize Arkormˣ by creating a default config file in the current directory";
1434
+ /**
1435
+ * Command handler for the init command.
1436
+ */
1437
+ async handle() {
1438
+ this.app.command = this;
1439
+ const outputDir = join$1(process.cwd(), "arkormx.config.js");
1440
+ const { stubs } = getUserConfig("paths") ?? {};
1441
+ const stubsDir = typeof stubs === "string" && stubs.trim().length > 0 ? stubs : getDefaultStubsPath();
1442
+ const preferredStubPath = join$1(stubsDir, "arkormx.config.stub");
1443
+ const legacyStubPath = join$1(stubsDir, "arkorm.config.stub");
1444
+ const stubPath = existsSync(preferredStubPath) ? preferredStubPath : legacyStubPath;
1445
+ if (existsSync(outputDir) && !this.option("force")) {
1446
+ this.error("Error: Arkormˣ has already been initialized. Use --force to reinitialize.");
1447
+ process.exit(1);
1448
+ }
1449
+ this.app.ensureDirectory(outputDir);
1450
+ if (existsSync(outputDir) && this.option("force")) copyFileSync(outputDir, outputDir.replace(/\.js$/, `.backup.${Date.now()}.js`));
1451
+ if (!existsSync(stubPath)) {
1452
+ this.error(`Error: Missing config stub at ${preferredStubPath} (or ${legacyStubPath})`);
1453
+ process.exit(1);
1454
+ }
1455
+ writeFileSync(outputDir, readFileSync(stubPath, "utf-8"));
1456
+ this.success("Arkormˣ initialized successfully!");
1457
+ }
1458
+ };
1459
+
1460
+ //#endregion
1461
+ //#region src/cli/commands/MakeFactoryCommand.ts
1462
+ /**
1463
+ * The MakeFactoryCommand class implements the CLI command for creating new factory classes.
1464
+ *
1465
+ * @author Legacy (3m1n3nc3)
1466
+ * @since 0.1.0
1467
+ */
1468
+ var MakeFactoryCommand = class extends Command {
1469
+ signature = `make:factory
16
1470
  {name : Name of the factory to create}
17
1471
  {--f|force : Overwrite existing file}
18
- `;description=`Create a new model factory class`;async handle(){this.app.command=this;let e=this.argument(`name`);if(!e)return void this.error(`Error: Name argument is required.`);let t=this.app.makeFactory(e,{force:this.option(`force`)});this.success(`Created factory: ${this.app.formatPathForLog(t.path)}`)}},ge=class extends S{signature=`make:migration
1472
+ `;
1473
+ description = "Create a new model factory class";
1474
+ /**
1475
+ * Command handler for the make:factory command.
1476
+ *
1477
+ * @returns
1478
+ */
1479
+ async handle() {
1480
+ this.app.command = this;
1481
+ const name = this.argument("name");
1482
+ if (!name) return void this.error("Error: Name argument is required.");
1483
+ const created = this.app.makeFactory(name, { force: this.option("force") });
1484
+ this.success(`Created factory: ${this.app.formatPathForLog(created.path)}`);
1485
+ }
1486
+ };
1487
+
1488
+ //#endregion
1489
+ //#region src/cli/commands/MakeMigrationCommand.ts
1490
+ /**
1491
+ * The MakeMigrationCommand class implements the CLI command for creating new migration classes.
1492
+ *
1493
+ * @author Legacy (3m1n3nc3)
1494
+ * @since 0.1.0
1495
+ */
1496
+ var MakeMigrationCommand = class extends Command {
1497
+ signature = `make:migration
19
1498
  {name : Name of the migration to create}
20
- `;description=`Create a new migration class file`;async handle(){this.app.command=this;let e=this.argument(`name`);if(!e)return void this.error(`Error: Name argument is required.`);let t=this.app.makeMigration(e);this.success(`Created migration: ${this.app.formatPathForLog(t.path)}`)}},_e=class extends S{signature=`make:model
1499
+ `;
1500
+ description = "Create a new migration class file";
1501
+ /**
1502
+ * Command handler for the make:migration command.
1503
+ *
1504
+ * @returns
1505
+ */
1506
+ async handle() {
1507
+ this.app.command = this;
1508
+ const name = this.argument("name");
1509
+ if (!name) return void this.error("Error: Name argument is required.");
1510
+ const created = this.app.makeMigration(name);
1511
+ this.success(`Created migration: ${this.app.formatPathForLog(created.path)}`);
1512
+ }
1513
+ };
1514
+
1515
+ //#endregion
1516
+ //#region src/cli/commands/MakeModelCommand.ts
1517
+ /**
1518
+ * The MakeModelCommand class implements the CLI command for creating new model
1519
+ * classes along with optional linked resources such as factories, seeders, and migrations.
1520
+ *
1521
+ * @author Legacy (3m1n3nc3)
1522
+ * @since 0.1.0
1523
+ */
1524
+ var MakeModelCommand = class extends Command {
1525
+ signature = `make:model
21
1526
  {name : Name of the model to create}
22
1527
  {--f|force : Overwrite existing files}
23
1528
  {--factory : Create and link a factory}
24
1529
  {--seeder : Create a seeder}
25
1530
  {--migration : Create a migration}
26
1531
  {--all : Create and link factory, seeder, and migration}
27
- `;description=`Create a new model and optional linked resources`;async handle(){this.app.command=this;let e=this.argument(`name`);if(!e)return void this.error(`Error: Name argument is required.`);let t=this.app.makeModel(e,this.options());this.success(`Created files:`),[[`Model`,t.model.path],[`Prisma schema ${t.prisma.updated?`(updated)`:`(already up to date)`}`,t.prisma.path],t.factory?[`Factory`,t.factory.path]:``,t.seeder?[`Seeder`,t.seeder.path]:``,t.migration?[`Migration`,t.migration.path]:``].filter(Boolean).map(([e,t])=>this.success(this.app.splitLogger(e,t)))}},ve=class extends S{signature=`make:seeder
1532
+ `;
1533
+ description = "Create a new model and optional linked resources";
1534
+ /**
1535
+ * Command handler for the make:model command.
1536
+ *
1537
+ * @returns
1538
+ */
1539
+ async handle() {
1540
+ this.app.command = this;
1541
+ const name = this.argument("name");
1542
+ if (!name) return void this.error("Error: Name argument is required.");
1543
+ const created = this.app.makeModel(name, this.options());
1544
+ this.success("Created files:");
1545
+ [
1546
+ ["Model", created.model.path],
1547
+ [`Prisma schema ${created.prisma.updated ? "(updated)" : "(already up to date)"}`, created.prisma.path],
1548
+ created.factory ? ["Factory", created.factory.path] : "",
1549
+ created.seeder ? ["Seeder", created.seeder.path] : "",
1550
+ created.migration ? ["Migration", created.migration.path] : ""
1551
+ ].filter(Boolean).map(([name, path]) => this.success(this.app.splitLogger(name, path)));
1552
+ }
1553
+ };
1554
+
1555
+ //#endregion
1556
+ //#region src/cli/commands/MakeSeederCommand.ts
1557
+ /**
1558
+ * The MakeSeederCommand class implements the CLI command for creating new seeder classes.
1559
+ *
1560
+ * @author Legacy (3m1n3nc3)
1561
+ * @since 0.1.0
1562
+ */
1563
+ var MakeSeederCommand = class extends Command {
1564
+ signature = `make:seeder
28
1565
  {name : Name of the seeder to create}
29
1566
  {--f|force : Overwrite existing file}
30
- `;description=`Create a new seeder class`;async handle(){this.app.command=this;let e=this.argument(`name`);if(!e)return void this.error(`Error: Name argument is required.`);let t=this.app.makeSeeder(e,this.options());this.success(`Created seeder: ${this.app.formatPathForLog(t.path)}`)}},ye=class extends S{signature=`migrate
1567
+ `;
1568
+ description = "Create a new seeder class";
1569
+ /**
1570
+ * Command handler for the make:seeder command.
1571
+ */
1572
+ async handle() {
1573
+ this.app.command = this;
1574
+ const name = this.argument("name");
1575
+ if (!name) return void this.error("Error: Name argument is required.");
1576
+ const created = this.app.makeSeeder(name, this.options());
1577
+ this.success(`Created seeder: ${this.app.formatPathForLog(created.path)}`);
1578
+ }
1579
+ };
1580
+
1581
+ //#endregion
1582
+ //#region src/cli/commands/MigrateCommand.ts
1583
+ /**
1584
+ * The MigrateCommand class implements the CLI command for applying migration
1585
+ * classes to the Prisma schema and running the Prisma workflow.
1586
+ *
1587
+ * @author Legacy (3m1n3nc3)
1588
+ * @since 0.1.0
1589
+ */
1590
+ var MigrateCommand = class extends Command {
1591
+ signature = `migrate
31
1592
  {name? : Migration class or file name}
32
1593
  {--all : Run all migrations from the configured migrations directory}
33
1594
  {--deploy : Use prisma migrate deploy instead of migrate dev}
@@ -35,17 +1596,266 @@ import{copyFileSync as e,existsSync as t,mkdirSync as n,readFileSync as r,readdi
35
1596
  {--skip-migrate : Skip prisma migrate command}
36
1597
  {--schema= : Explicit prisma schema path}
37
1598
  {--migration-name= : Name for prisma migrate dev}
38
- `;description=`Apply migration classes to schema.prisma and run Prisma workflow`;async handle(){this.app.command=this;let e=this.app.getConfig(`paths`)?.migrations??v(process.cwd(),`database`,`migrations`),t=this.app.resolveRuntimeDirectoryPath(e);if(!p(t))return void this.error(`Error: Migrations directory not found: ${this.app.formatPathForLog(e)}`);let n=this.option(`schema`)?y(String(this.option(`schema`))):v(process.cwd(),`prisma`,`schema.prisma`),r=this.option(`all`)?await this.loadAllMigrations(t):(await this.loadNamedMigration(t,this.argument(`name`))).filter(([e])=>e!==void 0);if(r.length===0)return void this.error(`Error: No migration classes found to run.`);for(let[e]of r)await W(e,{schemaPath:n,write:!0});this.option(`skip-generate`)||I([`generate`],process.cwd()),this.option(`skip-migrate`)||(this.option(`deploy`)?I([`migrate`,`deploy`],process.cwd()):I([`migrate`,`dev`,`--name`,this.option(`migration-name`)?String(this.option(`migration-name`)):`arkorm_cli_${Date.now()}`],process.cwd())),this.success(`Applied ${r.length} migration(s).`),r.forEach(([e,t])=>this.success(this.app.splitLogger(`Migrated`,t)))}async loadAllMigrations(e){let t=g(e).filter(e=>/\.(ts|js|mjs|cjs)$/i.test(e)).sort((e,t)=>e.localeCompare(t)).map(t=>this.app.resolveRuntimeScriptPath(v(e,t)));return(await Promise.all(t.map(async e=>(await this.loadMigrationClassesFromFile(e)).map(t=>[t,e])))).flat()}async loadNamedMigration(e,t){if(!t)return[[void 0,``]];let n=t.replace(/Migration$/,``),r=[`${t}.ts`,`${t}.js`,`${t}.mjs`,`${t}.cjs`,`${n}Migration.ts`,`${n}Migration.js`,`${n}Migration.mjs`,`${n}Migration.cjs`].map(t=>v(e,t)).find(e=>p(e));if(!r)return[[void 0,t]];let i=this.app.resolveRuntimeScriptPath(r);return(await this.loadMigrationClassesFromFile(i)).map(e=>[e,i])}async loadMigrationClassesFromFile(e){let t=await import(`${w(y(e)).href}?arkorm_migrate=${Date.now()}`);return Object.values(t).filter(e=>typeof e==`function`?e.prototype instanceof E:!1)}},be=class extends S{signature=`models:sync
1599
+ `;
1600
+ description = "Apply migration classes to schema.prisma and run Prisma workflow";
1601
+ /**
1602
+ * Command handler for the migrate command.
1603
+ * This method is responsible for orchestrating the migration
1604
+ * process, including loading migration classes, applying them to
1605
+ * the Prisma schema, and running the appropriate Prisma commands
1606
+ * based on the provided options.
1607
+ *
1608
+ * @returns
1609
+ */
1610
+ async handle() {
1611
+ this.app.command = this;
1612
+ const configuredMigrationsDir = this.app.getConfig("paths")?.migrations ?? join$1(process.cwd(), "database", "migrations");
1613
+ const migrationsDir = this.app.resolveRuntimeDirectoryPath(configuredMigrationsDir);
1614
+ if (!existsSync$1(migrationsDir)) return void this.error(`Error: Migrations directory not found: ${this.app.formatPathForLog(configuredMigrationsDir)}`);
1615
+ const schemaPath = this.option("schema") ? resolve(String(this.option("schema"))) : join$1(process.cwd(), "prisma", "schema.prisma");
1616
+ const classes = this.option("all") ? await this.loadAllMigrations(migrationsDir) : (await this.loadNamedMigration(migrationsDir, this.argument("name"))).filter(([cls]) => cls !== void 0);
1617
+ if (classes.length === 0) return void this.error("Error: No migration classes found to run.");
1618
+ for (const [MigrationClassItem] of classes) await applyMigrationToPrismaSchema(MigrationClassItem, {
1619
+ schemaPath,
1620
+ write: true
1621
+ });
1622
+ if (!this.option("skip-generate")) runPrismaCommand(["generate"], process.cwd());
1623
+ if (!this.option("skip-migrate")) if (this.option("deploy")) runPrismaCommand(["migrate", "deploy"], process.cwd());
1624
+ else runPrismaCommand([
1625
+ "migrate",
1626
+ "dev",
1627
+ "--name",
1628
+ this.option("migration-name") ? String(this.option("migration-name")) : `arkorm_cli_${Date.now()}`
1629
+ ], process.cwd());
1630
+ this.success(`Applied ${classes.length} migration(s).`);
1631
+ classes.forEach(([_, file]) => this.success(this.app.splitLogger("Migrated", file)));
1632
+ }
1633
+ /**
1634
+ * Load all migration classes from the specified directory.
1635
+ *
1636
+ * @param migrationsDir The directory to load migration classes from.
1637
+ */
1638
+ async loadAllMigrations(migrationsDir) {
1639
+ const files = readdirSync$1(migrationsDir).filter((file) => /\.(ts|js|mjs|cjs)$/i.test(file)).sort((left, right) => left.localeCompare(right)).map((file) => this.app.resolveRuntimeScriptPath(join$1(migrationsDir, file)));
1640
+ return (await Promise.all(files.map(async (file) => (await this.loadMigrationClassesFromFile(file)).map((cls) => [cls, file])))).flat();
1641
+ }
1642
+ /**
1643
+ * Load migration classes from a specific file or by class name.
1644
+ *
1645
+ * @param migrationsDir
1646
+ * @param name
1647
+ * @returns
1648
+ */
1649
+ async loadNamedMigration(migrationsDir, name) {
1650
+ if (!name) return [[void 0, ""]];
1651
+ const base = name.replace(/Migration$/, "");
1652
+ const target = [
1653
+ `${name}.ts`,
1654
+ `${name}.js`,
1655
+ `${name}.mjs`,
1656
+ `${name}.cjs`,
1657
+ `${base}Migration.ts`,
1658
+ `${base}Migration.js`,
1659
+ `${base}Migration.mjs`,
1660
+ `${base}Migration.cjs`
1661
+ ].map((file) => join$1(migrationsDir, file)).find((file) => existsSync$1(file));
1662
+ if (!target) return [[void 0, name]];
1663
+ const runtimeTarget = this.app.resolveRuntimeScriptPath(target);
1664
+ return (await this.loadMigrationClassesFromFile(runtimeTarget)).map((cls) => [cls, runtimeTarget]);
1665
+ }
1666
+ /**
1667
+ * Load migration classes from a given file path.
1668
+ *
1669
+ * @param filePath
1670
+ * @returns
1671
+ */
1672
+ async loadMigrationClassesFromFile(filePath) {
1673
+ const imported = await import(`${pathToFileURL$1(resolve(filePath)).href}?arkorm_migrate=${Date.now()}`);
1674
+ return Object.values(imported).filter((value) => {
1675
+ if (typeof value !== "function") return false;
1676
+ return value.prototype instanceof Migration;
1677
+ });
1678
+ }
1679
+ };
1680
+
1681
+ //#endregion
1682
+ //#region src/cli/commands/ModelsSyncCommand.ts
1683
+ var ModelsSyncCommand = class extends Command {
1684
+ signature = `models:sync
39
1685
  {--schema= : Path to prisma schema file}
40
1686
  {--models= : Path to models directory}
41
- `;description=`Sync model declare attributes from prisma schema for all model files`;async handle(){this.app.command=this;let e=this.app.syncModelsFromPrisma({schemaPath:this.option(`schema`)?y(String(this.option(`schema`))):void 0,modelsDir:this.option(`models`)?y(String(this.option(`models`))):void 0}),t=e.updated.length===0?[this.app.splitLogger(`Updated`,`none`)]:e.updated.map(e=>this.app.splitLogger(`Updated`,e));this.success(`SUCCESS: Model sync completed with the following results:`),[this.app.splitLogger(`Schema`,e.schemaPath),this.app.splitLogger(`Models`,e.modelsDir),this.app.splitLogger(`Processed`,String(e.total)),...t,this.app.splitLogger(`Skipped`,String(e.skipped.length))].map(e=>this.success(e))}},xe=class e{async call(...t){await e.runSeeders(...t)}static toSeederInstance(t){return t instanceof e?t:new t}static async runSeeders(...e){let t=e.reduce((e,t)=>Array.isArray(t)?[...e,...t]:(e.push(t),e),[]);for(let e of t)await this.toSeederInstance(e).run()}},Se=class extends S{signature=`seed
1687
+ `;
1688
+ description = "Sync model declare attributes from prisma schema for all model files";
1689
+ async handle() {
1690
+ this.app.command = this;
1691
+ const result = this.app.syncModelsFromPrisma({
1692
+ schemaPath: this.option("schema") ? resolve(String(this.option("schema"))) : void 0,
1693
+ modelsDir: this.option("models") ? resolve(String(this.option("models"))) : void 0
1694
+ });
1695
+ const updatedLines = result.updated.length === 0 ? [this.app.splitLogger("Updated", "none")] : result.updated.map((path) => this.app.splitLogger("Updated", path));
1696
+ this.success("SUCCESS: Model sync completed with the following results:");
1697
+ [
1698
+ this.app.splitLogger("Schema", result.schemaPath),
1699
+ this.app.splitLogger("Models", result.modelsDir),
1700
+ this.app.splitLogger("Processed", String(result.total)),
1701
+ ...updatedLines,
1702
+ this.app.splitLogger("Skipped", String(result.skipped.length))
1703
+ ].map((line) => this.success(line));
1704
+ }
1705
+ };
1706
+
1707
+ //#endregion
1708
+ //#region src/database/Seeder.ts
1709
+ /**
1710
+ * The Seeder class serves as a base for defining database seeders, which are
1711
+ * used to populate the database with initial or test data.
1712
+ *
1713
+ * @author Legacy (3m1n3nc3)
1714
+ * @since 0.1.0
1715
+ */
1716
+ var Seeder = class Seeder {
1717
+ /**
1718
+ * Runs one or more seeders.
1719
+ *
1720
+ * @param seeders The seeders to be run.
1721
+ */
1722
+ async call(...seeders) {
1723
+ await Seeder.runSeeders(...seeders);
1724
+ }
1725
+ /**
1726
+ * Converts a SeederInput into a Seeder instance.
1727
+ *
1728
+ * @param input The SeederInput to convert.
1729
+ * @returns A Seeder instance.
1730
+ */
1731
+ static toSeederInstance(input) {
1732
+ if (input instanceof Seeder) return input;
1733
+ return new input();
1734
+ }
1735
+ /**
1736
+ * Runs the given seeders in sequence.
1737
+ *
1738
+ * @param seeders The seeders to be run.
1739
+ */
1740
+ static async runSeeders(...seeders) {
1741
+ const queue = seeders.reduce((all, current) => {
1742
+ if (Array.isArray(current)) return [...all, ...current];
1743
+ all.push(current);
1744
+ return all;
1745
+ }, []);
1746
+ for (const seeder of queue) await this.toSeederInstance(seeder).run();
1747
+ }
1748
+ };
1749
+
1750
+ //#endregion
1751
+ //#region src/cli/commands/SeedCommand.ts
1752
+ /**
1753
+ * The SeedCommand class implements the CLI command for running seeder classes.
1754
+ *
1755
+ * @author Legacy (3m1n3nc3)
1756
+ * @since 0.1.0
1757
+ */
1758
+ var SeedCommand = class extends Command {
1759
+ signature = `seed
42
1760
  {name? : Seeder class or file name}
43
1761
  {--all : Run all seeders in the configured seeders directory}
44
- `;description=`Run one or more seeders`;async handle(){this.app.command=this;let e=this.app.getConfig(`paths`)?.seeders??v(process.cwd(),`database`,`seeders`),t=this.app.resolveRuntimeDirectoryPath(e);if(!p(t))return void this.error(`ERROR: Seeders directory not found: ${this.app.formatPathForLog(e)}`);let n=this.option(`all`)?await this.loadAllSeeders(t):await this.loadNamedSeeder(t,this.argument(`name`)??`DatabaseSeeder`);if(n.length===0)return void this.error(`ERROR: No seeder classes found to run.`);for(let e of n)await new e().run();this.success(`Database seeding completed`),n.forEach(e=>this.success(this.app.splitLogger(`Seeded`,e.name)))}async loadAllSeeders(e){let t=g(e).filter(e=>/\.(ts|js|mjs|cjs)$/i.test(e)).map(t=>this.app.resolveRuntimeScriptPath(v(e,t)));return(await Promise.all(t.map(async e=>await this.loadSeederClassesFromFile(e)))).flat()}async loadNamedSeeder(e,t){let n=t.replace(/Seeder$/,``),r=[`${t}.ts`,`${t}.js`,`${t}.mjs`,`${t}.cjs`,`${n}Seeder.ts`,`${n}Seeder.js`,`${n}Seeder.mjs`,`${n}Seeder.cjs`].map(t=>v(e,t)).find(e=>p(e));if(!r)return[];let i=this.app.resolveRuntimeScriptPath(r);return await this.loadSeederClassesFromFile(i)}async loadSeederClassesFromFile(e){let t=await import(`${w(y(e)).href}?arkorm_seed=${Date.now()}`);return Object.values(t).filter(e=>typeof e==`function`?e.prototype instanceof xe:!1)}},Ce=String.raw`
1762
+ `;
1763
+ description = "Run one or more seeders";
1764
+ /**
1765
+ * Command handler for the seed command.
1766
+ *
1767
+ * @returns
1768
+ */
1769
+ async handle() {
1770
+ this.app.command = this;
1771
+ const configuredSeedersDir = this.app.getConfig("paths")?.seeders ?? join$1(process.cwd(), "database", "seeders");
1772
+ const seedersDir = this.app.resolveRuntimeDirectoryPath(configuredSeedersDir);
1773
+ if (!existsSync$1(seedersDir)) return void this.error(`ERROR: Seeders directory not found: ${this.app.formatPathForLog(configuredSeedersDir)}`);
1774
+ const classes = this.option("all") ? await this.loadAllSeeders(seedersDir) : await this.loadNamedSeeder(seedersDir, this.argument("name") ?? "DatabaseSeeder");
1775
+ if (classes.length === 0) return void this.error("ERROR: No seeder classes found to run.");
1776
+ for (const SeederClassItem of classes) await new SeederClassItem().run();
1777
+ this.success("Database seeding completed");
1778
+ classes.forEach((cls) => this.success(this.app.splitLogger("Seeded", cls.name)));
1779
+ }
1780
+ /**
1781
+ * Load all seeder classes from the specified directory.
1782
+ *
1783
+ * @param seedersDir
1784
+ * @returns
1785
+ */
1786
+ async loadAllSeeders(seedersDir) {
1787
+ const files = readdirSync$1(seedersDir).filter((file) => /\.(ts|js|mjs|cjs)$/i.test(file)).map((file) => this.app.resolveRuntimeScriptPath(join$1(seedersDir, file)));
1788
+ return (await Promise.all(files.map(async (file) => await this.loadSeederClassesFromFile(file)))).flat();
1789
+ }
1790
+ /**
1791
+ * Load seeder classes from a specific file or by class name.
1792
+ *
1793
+ * @param seedersDir
1794
+ * @param name
1795
+ * @returns
1796
+ */
1797
+ async loadNamedSeeder(seedersDir, name) {
1798
+ const base = name.replace(/Seeder$/, "");
1799
+ const target = [
1800
+ `${name}.ts`,
1801
+ `${name}.js`,
1802
+ `${name}.mjs`,
1803
+ `${name}.cjs`,
1804
+ `${base}Seeder.ts`,
1805
+ `${base}Seeder.js`,
1806
+ `${base}Seeder.mjs`,
1807
+ `${base}Seeder.cjs`
1808
+ ].map((file) => join$1(seedersDir, file)).find((file) => existsSync$1(file));
1809
+ if (!target) return [];
1810
+ const runtimeTarget = this.app.resolveRuntimeScriptPath(target);
1811
+ return await this.loadSeederClassesFromFile(runtimeTarget);
1812
+ }
1813
+ /**
1814
+ * Load seeder classes from a given file path.
1815
+ *
1816
+ * @param filePath The path to the file containing seeder classes.
1817
+ * @returns An array of seeder classes.
1818
+ */
1819
+ async loadSeederClassesFromFile(filePath) {
1820
+ const imported = await import(`${pathToFileURL$1(resolve(filePath)).href}?arkorm_seed=${Date.now()}`);
1821
+ return Object.values(imported).filter((value) => {
1822
+ if (typeof value !== "function") return false;
1823
+ return value.prototype instanceof Seeder;
1824
+ });
1825
+ }
1826
+ };
1827
+
1828
+ //#endregion
1829
+ //#region src/cli/logo.ts
1830
+ var logo_default = String.raw`
45
1831
  __/^^^^^^^^^^^^^^^^\__
46
1832
  ▄▄▄/ \▄▄
47
1833
  ▄██▀▀██▄ ▄▄
48
1834
  ███ ███ ████▄ ██ ▄█▀ ▄███▄ ████▄ ███▄███▄
49
1835
  ███▀▀███ ██ ▀▀ ████ ██ ██ ██ ▀▀ ██ ██ ██
50
1836
  ███ ███ ██ ██ ▀█▄ ▀███▀ ██ ██ ██ ██
51
- `;const we=new me;await C.init(we,{logo:Ce,name:`Arkormˣ CLI`,baseCommands:[$,_e,he,ve,ge,be,Se,ye],exceptionHandler(e){throw e}});export{};
1837
+ `;
1838
+
1839
+ //#endregion
1840
+ //#region src/cli/index.ts
1841
+ const app = new CliApp();
1842
+ await Kernel.init(app, {
1843
+ logo: logo_default,
1844
+ name: "Arkormˣ CLI",
1845
+ baseCommands: [
1846
+ InitCommand,
1847
+ MakeModelCommand,
1848
+ MakeFactoryCommand,
1849
+ MakeSeederCommand,
1850
+ MakeMigrationCommand,
1851
+ ModelsSyncCommand,
1852
+ SeedCommand,
1853
+ MigrateCommand
1854
+ ],
1855
+ exceptionHandler(exception) {
1856
+ throw exception;
1857
+ }
1858
+ });
1859
+
1860
+ //#endregion
1861
+ export { };