arkormx 1.3.0 → 1.3.2

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
@@ -5,13 +5,14 @@ import { spawnSync } from "node:child_process";
5
5
  import { str } from "@h3ravel/support";
6
6
  import path, { dirname as dirname$1, extname as extname$1, join as join$1, relative } from "path";
7
7
  import { copyFileSync, existsSync as existsSync$1, mkdirSync as mkdirSync$1, readFileSync as readFileSync$1, readdirSync as readdirSync$1, rmSync as rmSync$1, writeFileSync as writeFileSync$1 } from "fs";
8
- import { fileURLToPath, pathToFileURL } from "url";
9
8
  import { AsyncLocalStorage } from "async_hooks";
9
+ import { createJiti } from "@rexxars/jiti";
10
+ import { pathToFileURL } from "node:url";
10
11
  import { createRequire } from "module";
12
+ import { fileURLToPath } from "url";
11
13
  import { Logger } from "@h3ravel/shared";
12
14
  import { Command, Kernel } from "@h3ravel/musket";
13
15
  import { createHash } from "node:crypto";
14
- import { pathToFileURL as pathToFileURL$1 } from "node:url";
15
16
 
16
17
  //#region src/Exceptions/ArkormException.ts
17
18
  var ArkormException = class extends Error {
@@ -1413,6 +1414,18 @@ const applyMigrationRollbackToPrismaSchema = async (migration, options = {}) =>
1413
1414
  };
1414
1415
  };
1415
1416
 
1417
+ //#endregion
1418
+ //#region src/helpers/runtime-module-loader.ts
1419
+ var RuntimeModuleLoader = class {
1420
+ static async load(filePath) {
1421
+ const resolvedPath = resolve(filePath);
1422
+ return await createJiti(pathToFileURL(resolvedPath).href, {
1423
+ interopDefault: false,
1424
+ tsconfigPaths: true
1425
+ }).import(resolvedPath);
1426
+ }
1427
+ };
1428
+
1416
1429
  //#endregion
1417
1430
  //#region src/helpers/runtime-config.ts
1418
1431
  const resolveDefaultStubsPath = () => {
@@ -1516,7 +1529,7 @@ const resolveAndApplyConfig = (imported) => {
1516
1529
  * @returns A promise that resolves to the imported configuration module.
1517
1530
  */
1518
1531
  const importConfigFile = (configPath) => {
1519
- return import(`${pathToFileURL(configPath).href}?arkorm_runtime=${Date.now()}`);
1532
+ return RuntimeModuleLoader.load(configPath);
1520
1533
  };
1521
1534
  const loadRuntimeConfigSync = () => {
1522
1535
  const require = createRequire(import.meta.url);
@@ -1870,10 +1883,225 @@ var CliApp = class {
1870
1883
  if (value === "String") return "string";
1871
1884
  if (value === "Boolean") return "boolean";
1872
1885
  if (value === "DateTime") return "Date";
1873
- if (value === "Json") return "Record<string, unknown>";
1886
+ if (value === "Json") return "Record<string, unknown> | unknown[]";
1874
1887
  if (value === "Bytes") return "Buffer";
1875
1888
  return "unknown";
1876
1889
  }
1890
+ splitTopLevel(value, delimiter) {
1891
+ const parts = [];
1892
+ let start = 0;
1893
+ let angleDepth = 0;
1894
+ let parenthesisDepth = 0;
1895
+ let quote = null;
1896
+ for (let index = 0; index < value.length; index += 1) {
1897
+ const character = value[index];
1898
+ const previous = index > 0 ? value[index - 1] : "";
1899
+ if (quote) {
1900
+ if (character === quote && previous !== "\\") quote = null;
1901
+ continue;
1902
+ }
1903
+ if (character === "'" || character === "\"") {
1904
+ quote = character;
1905
+ continue;
1906
+ }
1907
+ if (character === "<") {
1908
+ angleDepth += 1;
1909
+ continue;
1910
+ }
1911
+ if (character === ">") {
1912
+ angleDepth = Math.max(0, angleDepth - 1);
1913
+ continue;
1914
+ }
1915
+ if (character === "(") {
1916
+ parenthesisDepth += 1;
1917
+ continue;
1918
+ }
1919
+ if (character === ")") {
1920
+ parenthesisDepth = Math.max(0, parenthesisDepth - 1);
1921
+ continue;
1922
+ }
1923
+ if (character === delimiter && angleDepth === 0 && parenthesisDepth === 0) {
1924
+ parts.push(value.slice(start, index).trim());
1925
+ start = index + 1;
1926
+ }
1927
+ }
1928
+ parts.push(value.slice(start).trim());
1929
+ return parts.filter(Boolean);
1930
+ }
1931
+ hasWrappedParentheses(value) {
1932
+ if (!value.startsWith("(") || !value.endsWith(")")) return false;
1933
+ let depth = 0;
1934
+ let quote = null;
1935
+ for (let index = 0; index < value.length; index += 1) {
1936
+ const character = value[index];
1937
+ const previous = index > 0 ? value[index - 1] : "";
1938
+ if (quote) {
1939
+ if (character === quote && previous !== "\\") quote = null;
1940
+ continue;
1941
+ }
1942
+ if (character === "'" || character === "\"") {
1943
+ quote = character;
1944
+ continue;
1945
+ }
1946
+ if (character === "(") depth += 1;
1947
+ if (character === ")") {
1948
+ depth -= 1;
1949
+ if (depth === 0 && index < value.length - 1) return false;
1950
+ }
1951
+ }
1952
+ return depth === 0;
1953
+ }
1954
+ stripWrappedParentheses(value) {
1955
+ let nextValue = value.trim();
1956
+ while (this.hasWrappedParentheses(nextValue)) nextValue = nextValue.slice(1, -1).trim();
1957
+ return nextValue;
1958
+ }
1959
+ parseDeclarationType(value) {
1960
+ const trimmed = this.stripWrappedParentheses(value.trim());
1961
+ if (!trimmed) return null;
1962
+ const unionParts = this.splitTopLevel(trimmed, "|");
1963
+ if (unionParts.length > 1) {
1964
+ const types = unionParts.map((part) => this.parseDeclarationType(part)).filter((part) => part !== null);
1965
+ if (types.length !== unionParts.length) return null;
1966
+ return {
1967
+ kind: "union",
1968
+ types: types.flatMap((type) => type.kind === "union" ? type.types : [type])
1969
+ };
1970
+ }
1971
+ if (trimmed.endsWith("[]")) {
1972
+ const element = this.parseDeclarationType(trimmed.slice(0, -2));
1973
+ if (!element) return null;
1974
+ return {
1975
+ kind: "array",
1976
+ element
1977
+ };
1978
+ }
1979
+ const arrayMatch = trimmed.match(/^(Array|ReadonlyArray)<([\s\S]+)>$/);
1980
+ if (arrayMatch) {
1981
+ const element = this.parseDeclarationType(arrayMatch[2]);
1982
+ if (!element) return null;
1983
+ return {
1984
+ kind: "array",
1985
+ element
1986
+ };
1987
+ }
1988
+ if (trimmed === "null") return { kind: "null" };
1989
+ if (trimmed.startsWith("'") && trimmed.endsWith("'") || trimmed.startsWith("\"") && trimmed.endsWith("\"")) return {
1990
+ kind: "string-literal",
1991
+ value: trimmed.slice(1, -1)
1992
+ };
1993
+ if (trimmed === "Record<string, unknown>") return {
1994
+ kind: "named",
1995
+ name: trimmed
1996
+ };
1997
+ if (/^[A-Za-z_$][A-Za-z0-9_$]*$/.test(trimmed)) return {
1998
+ kind: "named",
1999
+ name: trimmed
2000
+ };
2001
+ return null;
2002
+ }
2003
+ expandUnion(node) {
2004
+ return node.kind === "union" ? node.types : [node];
2005
+ }
2006
+ isEnumTypeName(value, enums) {
2007
+ return enums.has(value);
2008
+ }
2009
+ isDeclarationNodeAssignable(actual, expected, enums) {
2010
+ if (expected.kind === "named") {
2011
+ if (expected.name === "unknown") return true;
2012
+ if (actual.kind === "named") {
2013
+ if (actual.name === expected.name) return true;
2014
+ if (expected.name === "string" && this.isEnumTypeName(actual.name, enums)) return true;
2015
+ }
2016
+ if (actual.kind === "string-literal") {
2017
+ if (expected.name === "string") return true;
2018
+ if (enums.get(expected.name)?.includes(actual.value)) return true;
2019
+ }
2020
+ return false;
2021
+ }
2022
+ if (expected.kind === "array") return actual.kind === "array" && this.isDeclarationAssignable(actual.element, expected.element, enums);
2023
+ if (expected.kind === "null") return actual.kind === "null";
2024
+ if (expected.kind === "string-literal") return actual.kind === "string-literal" && actual.value === expected.value;
2025
+ return false;
2026
+ }
2027
+ isDeclarationAssignable(actual, expected, enums) {
2028
+ const actualTypes = this.expandUnion(actual);
2029
+ const expectedTypes = this.expandUnion(expected);
2030
+ return actualTypes.every((actualType) => {
2031
+ return expectedTypes.some((expectedType) => {
2032
+ return this.isDeclarationNodeAssignable(actualType, expectedType, enums);
2033
+ });
2034
+ });
2035
+ }
2036
+ isCompatibleDeclarationType(actualType, expectedType, enums) {
2037
+ const actual = this.parseDeclarationType(actualType);
2038
+ const expected = this.parseDeclarationType(expectedType);
2039
+ if (!actual || !expected) return actualType.replace(/\s+/g, " ").trim() === expectedType.replace(/\s+/g, " ").trim();
2040
+ return this.isDeclarationAssignable(actual, expected, enums);
2041
+ }
2042
+ collectEnumReferencesFromNode(node, enums, collected) {
2043
+ if (node.kind === "named" && enums.has(node.name)) {
2044
+ collected.add(node.name);
2045
+ return;
2046
+ }
2047
+ if (node.kind === "array") {
2048
+ this.collectEnumReferencesFromNode(node.element, enums, collected);
2049
+ return;
2050
+ }
2051
+ if (node.kind === "union") node.types.forEach((type) => this.collectEnumReferencesFromNode(type, enums, collected));
2052
+ }
2053
+ collectEnumReferences(type, enums) {
2054
+ const parsed = this.parseDeclarationType(type);
2055
+ if (!parsed) return [];
2056
+ const collected = /* @__PURE__ */ new Set();
2057
+ this.collectEnumReferencesFromNode(parsed, enums, collected);
2058
+ return [...collected].sort((left, right) => left.localeCompare(right));
2059
+ }
2060
+ syncPrismaEnumImports(modelSource, enumTypes) {
2061
+ if (enumTypes.length === 0) return modelSource;
2062
+ const importRegex = /^import\s+type\s+\{([^}]+)\}\s+from\s+['"]@prisma\/client['"]\s*;?$/m;
2063
+ const existingImport = modelSource.match(importRegex);
2064
+ if (existingImport) {
2065
+ const existingTypes = existingImport[1].split(",").map((value) => value.trim()).filter(Boolean);
2066
+ const mergedTypes = [...new Set([...existingTypes, ...enumTypes])].sort((left, right) => left.localeCompare(right));
2067
+ return modelSource.replace(importRegex, `import type { ${mergedTypes.join(", ")} } from '@prisma/client'`);
2068
+ }
2069
+ const lines = modelSource.split("\n");
2070
+ let insertionIndex = 0;
2071
+ while (insertionIndex < lines.length && lines[insertionIndex].trim().startsWith("import ")) insertionIndex += 1;
2072
+ lines.splice(insertionIndex, 0, `import type { ${enumTypes.join(", ")} } from '@prisma/client'`);
2073
+ return lines.join("\n");
2074
+ }
2075
+ /**
2076
+ * Parse Prisma enum definitions from a schema and return their member names.
2077
+ *
2078
+ * @param schema The Prisma schema source.
2079
+ * @returns A map of enum names to their declared member names.
2080
+ */
2081
+ parsePrismaEnums(schema) {
2082
+ const enums = /* @__PURE__ */ new Map();
2083
+ for (const match of schema.matchAll(PRISMA_ENUM_REGEX)) {
2084
+ const enumName = match[1];
2085
+ const values = match[0].split("\n").slice(1, -1).map((line) => line.trim()).filter((line) => Boolean(line) && !line.startsWith("//")).map((line) => {
2086
+ return line.match(/^([A-Za-z][A-Za-z0-9_]*)\b/)?.[1];
2087
+ }).filter((value) => Boolean(value));
2088
+ enums.set(enumName, values);
2089
+ }
2090
+ return enums;
2091
+ }
2092
+ /**
2093
+ * Resolve the generated TypeScript declaration type for a Prisma field.
2094
+ *
2095
+ * @param fieldType The Prisma field type token.
2096
+ * @param isList Whether the field is declared as a Prisma list.
2097
+ * @param enums Known Prisma enum definitions.
2098
+ * @returns The declaration type to emit, or null when unsupported.
2099
+ */
2100
+ prismaFieldTypeToTs(fieldType, isList, enums) {
2101
+ const baseType = enums.has(fieldType) ? fieldType : this.prismaTypeToTs(fieldType);
2102
+ if (baseType === "unknown" && !enums.has(fieldType)) return null;
2103
+ return isList ? `Array<${baseType}>` : baseType;
2104
+ }
1877
2105
  /**
1878
2106
  * Parse the Prisma schema to extract model definitions and their fields, focusing
1879
2107
  * on scalar types.
@@ -1883,6 +2111,7 @@ var CliApp = class {
1883
2111
  */
1884
2112
  parsePrismaModels(schema) {
1885
2113
  const models = [];
2114
+ const enumDefinitions = this.parsePrismaEnums(schema);
1886
2115
  const modelRegex = /model\s+(\w+)\s*\{([\s\S]*?)\n\}/g;
1887
2116
  const scalarTypes = new Set([
1888
2117
  "Int",
@@ -1903,14 +2132,16 @@ var CliApp = class {
1903
2132
  body.split("\n").forEach((rawLine) => {
1904
2133
  const line = rawLine.trim();
1905
2134
  if (!line || line.startsWith("@@") || line.startsWith("//")) return;
1906
- const fieldMatch = line.match(/^(\w+)\s+([A-Za-z]+)(\?)?(?:\s|$)/);
2135
+ const fieldMatch = line.match(/^(\w+)\s+([A-Za-z][A-Za-z0-9_]*)(\[\])?(\?)?(?:\s|$)/);
1907
2136
  if (!fieldMatch) return;
1908
2137
  const fieldType = fieldMatch[2];
1909
- if (!scalarTypes.has(fieldType)) return;
2138
+ if (!scalarTypes.has(fieldType) && !enumDefinitions.has(fieldType)) return;
2139
+ const declarationType = this.prismaFieldTypeToTs(fieldType, Boolean(fieldMatch[3]), enumDefinitions);
2140
+ if (!declarationType) return;
1910
2141
  fields.push({
1911
2142
  name: fieldMatch[1],
1912
- type: this.prismaTypeToTs(fieldType),
1913
- nullable: Boolean(fieldMatch[3])
2143
+ type: declarationType,
2144
+ nullable: Boolean(fieldMatch[4])
1914
2145
  });
1915
2146
  });
1916
2147
  models.push({
@@ -1931,7 +2162,7 @@ var CliApp = class {
1931
2162
  * @param declarations A list of attribute declarations to sync.
1932
2163
  * @returns An object containing the updated content and a flag indicating if it was updated.
1933
2164
  */
1934
- syncModelDeclarations(modelSource, declarations) {
2165
+ syncModelDeclarations(modelSource, declarations, enums) {
1935
2166
  const lines = modelSource.split("\n");
1936
2167
  const classIndex = lines.findIndex((line) => /export\s+class\s+\w+\s+extends\s+Model<.+>\s*\{/.test(line));
1937
2168
  if (classIndex < 0) return {
@@ -1953,16 +2184,38 @@ var CliApp = class {
1953
2184
  content: modelSource,
1954
2185
  updated: false
1955
2186
  };
1956
- const withoutDeclares = lines.slice(classIndex + 1, classEndIndex).filter((line) => !/^\s*declare\s+\w+\??:\s*[^\n]+$/.test(line));
1957
- const rebuiltClass = [...declarations.map((declaration) => ` ${declaration}`), ...withoutDeclares];
2187
+ const withinClass = lines.slice(classIndex + 1, classEndIndex);
2188
+ const existingDeclarations = /* @__PURE__ */ new Map();
2189
+ withinClass.forEach((line) => {
2190
+ const declarationMatch = line.match(/^\s*declare\s+(\w+)\??:\s*([^;\n]+);?\s*$/);
2191
+ if (!declarationMatch) return;
2192
+ existingDeclarations.set(declarationMatch[1], {
2193
+ name: declarationMatch[1],
2194
+ raw: line.trim(),
2195
+ type: declarationMatch[2].trim()
2196
+ });
2197
+ });
2198
+ const withoutDeclares = withinClass.filter((line) => !/^\s*declare\s+\w+\??:\s*[^\n]+$/.test(line));
2199
+ const chosenDeclarations = declarations.map((declaration) => {
2200
+ const expectedType = `${declaration.type}${declaration.nullable ? " | null" : ""}`;
2201
+ const existingDeclaration = existingDeclarations.get(declaration.name);
2202
+ if (existingDeclaration && this.isCompatibleDeclarationType(existingDeclaration.type, expectedType, enums)) return existingDeclaration.raw;
2203
+ return `declare ${declaration.name}: ${expectedType}`;
2204
+ });
2205
+ const rebuiltClass = [...chosenDeclarations.map((declaration) => ` ${declaration}`), ...withoutDeclares];
1958
2206
  const content = [
1959
2207
  ...lines.slice(0, classIndex + 1),
1960
2208
  ...rebuiltClass,
1961
2209
  ...lines.slice(classEndIndex)
1962
2210
  ].join("\n");
2211
+ const enumImports = [...new Set(chosenDeclarations.flatMap((declaration) => {
2212
+ const type = declaration.replace(/^declare\s+\w+\??:\s*/, "").replace(/;$/, "").trim();
2213
+ return this.collectEnumReferences(type, enums);
2214
+ }))].sort((left, right) => left.localeCompare(right));
2215
+ const contentWithImports = this.syncPrismaEnumImports(content, enumImports);
1963
2216
  return {
1964
- content,
1965
- updated: content !== modelSource
2217
+ content: contentWithImports,
2218
+ updated: contentWithImports !== modelSource
1966
2219
  };
1967
2220
  }
1968
2221
  /**
@@ -1982,6 +2235,7 @@ var CliApp = class {
1982
2235
  if (!existsSync$1(schemaPath)) throw new Error(`Prisma schema file not found: ${schemaPath}`);
1983
2236
  if (!existsSync$1(modelsDir)) throw new Error(`Models directory not found: ${modelsDir}`);
1984
2237
  const schema = readFileSync$1(schemaPath, "utf-8");
2238
+ const prismaEnums = this.parsePrismaEnums(schema);
1985
2239
  const prismaModels = this.parsePrismaModels(schema);
1986
2240
  const modelFiles = readdirSync$1(modelsDir).filter((file) => file.endsWith(".ts"));
1987
2241
  const updated = [];
@@ -2001,8 +2255,7 @@ var CliApp = class {
2001
2255
  skipped.push(filePath);
2002
2256
  return;
2003
2257
  }
2004
- const declarations = prismaModel.fields.map((field) => `declare ${field.name}: ${field.type}${field.nullable ? " | null" : ""}`);
2005
- const synced = this.syncModelDeclarations(source, declarations);
2258
+ const synced = this.syncModelDeclarations(source, prismaModel.fields, prismaEnums);
2006
2259
  if (!synced.updated) {
2007
2260
  skipped.push(filePath);
2008
2261
  return;
@@ -2424,7 +2677,7 @@ var MigrateCommand = class extends Command {
2424
2677
  * @returns
2425
2678
  */
2426
2679
  async loadMigrationClassesFromFile(filePath) {
2427
- const imported = await import(`${pathToFileURL$1(resolve(filePath)).href}?arkorm_migrate=${Date.now()}`);
2680
+ const imported = await RuntimeModuleLoader.load(filePath);
2428
2681
  return Object.values(imported).filter((value) => {
2429
2682
  if (typeof value !== "function") return false;
2430
2683
  const candidate = value;
@@ -2509,7 +2762,7 @@ var MigrateRollbackCommand = class extends Command {
2509
2762
  return (await Promise.all(files.map(async (file) => (await this.loadMigrationClassesFromFile(file)).map((cls) => [cls, file])))).flat();
2510
2763
  }
2511
2764
  async loadMigrationClassesFromFile(filePath) {
2512
- const imported = await import(`${pathToFileURL$1(resolve(filePath)).href}?arkorm_rollback=${Date.now()}`);
2765
+ const imported = await RuntimeModuleLoader.load(filePath);
2513
2766
  return Object.values(imported).filter((value) => {
2514
2767
  if (typeof value !== "function") return false;
2515
2768
  const candidate = value;
@@ -2719,7 +2972,7 @@ var SeedCommand = class extends Command {
2719
2972
  * @returns An array of seeder classes.
2720
2973
  */
2721
2974
  async loadSeederClassesFromFile(filePath) {
2722
- const imported = await import(`${pathToFileURL$1(resolve(filePath)).href}?arkorm_seed=${Date.now()}`);
2975
+ const imported = await RuntimeModuleLoader.load(filePath);
2723
2976
  return Object.values(imported).filter((value) => {
2724
2977
  if (typeof value !== "function") return false;
2725
2978
  const candidate = value;