diff --git a/scripts/validateImports.cs b/scripts/validateImports.cs
new file mode 100755
index 00000000..cf3252bd
--- /dev/null
+++ b/scripts/validateImports.cs
@@ -0,0 +1,200 @@
+#!/usr/bin/env dotnet
+#:property Nullable=enable
+#:property PublishAOT=false
+#:package ArcaneLibs@1.0.1-preview.20260616-220331
+
+using System.Text.Json.Serialization;
+using System.Text.RegularExpressions;
+using ArcaneLibs;
+using ArcaneLibs.Attributes;
+using ArcaneLibs.Extensions;
+
+var logJson = false;
+
+var importErrors = new Dictionary<string, List<ImportReference>>();
+
+void LogError(string fileName, ImportReference import)
+{
+ importErrors.GetOrCreate(fileName).Add(import);
+ Console.Write(
+ $"\n{ConsoleUtils.ColoredString("!", 255, 49, 49)} {ConsoleUtils.ColoredString(fileName + ":" + import.Line + ":" + import.Position, 49 + 128, 49 + 128, 255)}: {(logJson ? import.ToJson(indent: false) : import.ToColorizedString())}");
+}
+
+foreach (var f in ReadDirRecursive("./src"))
+{
+ if (f.EndsWith(".test.ts"))
+ {
+ }
+ else
+ await foreach (var import in GetImports(f))
+ {
+ // if (import.ImportSourceType == ImportSourceType.Npm)
+ // Console.WriteLine(import.ToJson(indent: false));
+
+ // file level
+ if (f == "./src/schemas/Validator.ts")
+ {
+ if (import is not { ImportSource: "ajv" or "ajv-formats" or "node:fs" or "node:path" })
+ LogError(f, import);
+ }
+
+ //directory level
+ else if (f.StartsWith("./src/extensions"))
+ {
+ if (import.ImportSourceType == ImportSourceTypeValue.LocalPath) continue;
+ LogError(f, import);
+ }
+ else if (f.StartsWith("./src/database"))
+ {
+ if (import.ImportSource == "typeorm"
+ || import.ImportSourceType == ImportSourceTypeValue.LocalPath
+ || import.ImportSource == "node:crypto"
+ || (import.ImportSource == "schemas" && import.IsTypeImport)
+ || (import.ImportSource == "../Database") // maybe?
+ )
+ continue;
+ LogError(f, import);
+ }
+ else if (f.StartsWith("./src/schemas"))
+ {
+ LogError(f, import);
+ }
+ else
+ {
+ }
+ }
+
+ if (importErrors.ContainsKey(f)) Console.Write("\n");
+}
+
+if (importErrors.Any())
+ Console.WriteLine($"\rFinished with {importErrors.Sum(x => x.Value.Count)} warnings...\e[K");
+
+Console.WriteLine();
+// await foreach (var import in GetImports("./src/apply-migrations.ts"))
+// {
+// Console.WriteLine(import.ToJson(indent: false));
+// }
+
+IEnumerable<string> ReadDirRecursive(string path)
+{
+ foreach (var f in Directory.GetFiles(path))
+ yield return f;
+
+ foreach (var d in Directory.GetDirectories(path))
+ foreach (var f in ReadDirRecursive(d))
+ yield return f;
+}
+
+async IAsyncEnumerable<ImportReference> GetImports(string path)
+{
+ var basicImportRegex = new Regex(
+ @"^import (?<typeSpecifier>type )?{?(?<entities>[a-zA-Z ,\n]+)}? from ""(?<source>.*)"";",
+ RegexOptions.Multiline
+ );
+ var basicRequireRegex = new Regex(
+ @"require\(""(?<source>.*)""\)",
+ RegexOptions.Multiline
+ );
+ // Console.WriteLine(basicImportRegex);
+
+ Console.Write($"\rReading imports for {path}: \e[K");
+ var fileContents = await File.ReadAllTextAsync(path);
+ Console.Write($"{fileContents.Length} chars");
+
+ // if (basicImportRegex.IsMatch(fileContents)) Console.WriteLine("Match!");
+ // else Console.WriteLine(fileContents + "\n^ Did not match regex " + basicImportRegex);
+
+ ImportSourceTypeValue ClassifyImportSourceType(string p)
+ {
+ if (p.StartsWith("node:")) return ImportSourceTypeValue.Node;
+ if (p.StartsWith("@types/")) return ImportSourceTypeValue.Types;
+ if (p.StartsWith("@spacebar/")) return ImportSourceTypeValue.SpacebarAlias;
+ if (p.StartsWith("lambert-server")) return ImportSourceTypeValue.VendoredModule;
+ if (p.StartsWith("../")) return ImportSourceTypeValue.Path;
+ if (p.StartsWith("./")) return ImportSourceTypeValue.LocalPath;
+
+ return ImportSourceTypeValue.Npm;
+ }
+
+ ImportReference GetRef(Match m, bool isRequire) => new()
+ {
+ IsRequire = isRequire,
+ IsTypeImport = m.Groups.ContainsKey("typeSpecifier") && m.Groups["typeSpecifier"].Success,
+ ImportEntities = m.Groups["entities"].Value.ReplaceLineEndings("").Replace(" ", "").Split(","),
+ ImportSource = m.Groups["source"].Value,
+ ImportSourceType = ClassifyImportSourceType(m.Groups["source"].Value),
+ Line = fileContents[..(m.Index)].CountInstances("\n"),
+ Position = m.Index - fileContents[..(m.Index)].LastIndexOf('\n')
+ };
+
+ foreach (Match m in basicImportRegex.Matches(fileContents))
+ yield return GetRef(m, false);
+
+ foreach (Match m in basicRequireRegex.Matches(fileContents))
+ yield return GetRef(m, true);
+}
+
+struct ImportReference
+{
+ public int Line { get; set; }
+ public int Position { get; set; }
+ public bool IsTypeImport { get; set; }
+ public bool IsRequire { get; set; }
+ public string[] ImportEntities { get; set; }
+ public string ImportSource { get; set; }
+ public ImportSourceTypeValue ImportSourceType { get; set; }
+
+ public override string ToString()
+ {
+ var sourceType = ImportSourceType;
+ return
+ $"{ImportSourceType.GetType().GetMember((sourceType.ToString())).First().GetFriendlyName()} {(IsRequire ? "require" : "import")} from {ImportSource}, obtaining {string.Join(", ", ImportEntities)}";
+ }
+
+ public string ToColorizedString()
+ {
+ var sourceType = ImportSourceType.GetType().GetMember((ImportSourceType.ToString())).First();
+ var sourceTypeClr = sourceType.GetColorOrNull();
+ return
+ $"{ConsoleUtils.ColoredString(sourceType.GetFriendlyName(), sourceTypeClr?.R ?? 255, sourceTypeClr?.G ?? 255, sourceTypeClr?.B ?? 255)} "
+ + $"{(IsRequire ? ConsoleUtils.ColoredString("require", 255, 255, 0) : ConsoleUtils.ColoredString("import", 0, 255, 0))} "
+ + $"from {ConsoleUtils.ColoredString(ImportSource, sourceTypeClr?.R ?? 255, sourceTypeClr?.G ?? 255, sourceTypeClr?.B ?? 255)}, "
+ + $"obtaining {(IsRequire ? "<unknown>" : string.Join(", ", ImportEntities.Select(x => ConsoleUtils.ColoredString(x, 150, 150, 150))))}";
+ }
+}
+
+enum ImportType
+{
+ Unknown,
+ Import,
+ ImportType,
+ Require
+}
+
+[JsonConverter(typeof(JsonStringEnumConverter<ImportSourceTypeValue>))]
+enum ImportSourceTypeValue
+{
+ Unknown,
+
+ [FriendlyName(Name = "NPM")] [Color(203, 56, 55)]
+ Npm,
+
+ [FriendlyName(Name = "NodeJS")] [Color(60, 135, 58)]
+ Node,
+
+ [FriendlyName(Name = "Types")] [Color(49, 120, 198)]
+ Types,
+
+ [FriendlyName(Name = "Spacebar internals")] [Color(11, 133, 255)]
+ SpacebarAlias,
+
+ [FriendlyName(Name = "Vendored module")] [Color(127, 255, 0)]
+ VendoredModule,
+
+ [FriendlyName(Name = "File path")] [Color(0, 255, 0)]
+ Path,
+
+ [Color(255, 255, 0)] [FriendlyName(Name = "Local file path")]
+ LocalPath,
+}
\ No newline at end of file
|