@boon4681/giri 0.0.2-alpha-8 → 0.0.3-alpha-1

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.js CHANGED
@@ -46,13 +46,7 @@ var init_es5 = __esm({
46
46
  });
47
47
 
48
48
  // src/generator/schema/program.ts
49
- function leanOptions(options) {
50
- return {
51
- ...options,
52
- types: []
53
- };
54
- }
55
- function createSchemaProgram(paths, routeFiles, programOptions = {}) {
49
+ function createSchemaProgram(paths, routeFiles) {
56
50
  let options = { ...DEFAULT_OPTIONS };
57
51
  const configPath = import_typescript2.default.findConfigFile(paths.cwd, import_typescript2.default.sys.fileExists, "tsconfig.json");
58
52
  if (configPath) {
@@ -65,9 +59,6 @@ function createSchemaProgram(paths, routeFiles, programOptions = {}) {
65
59
  options = { ...parsed.options, noEmit: true };
66
60
  }
67
61
  }
68
- if (programOptions.lean) {
69
- options = leanOptions(options);
70
- }
71
62
  return import_typescript2.default.createProgram(routeFiles, options);
72
63
  }
73
64
  var import_typescript2, DEFAULT_OPTIONS;
@@ -410,9 +401,9 @@ var init_schema = __esm({
410
401
 
411
402
  // src/cli.ts
412
403
  var import_node_child_process = require("child_process");
413
- var import_node_fs10 = require("fs");
414
- var import_promises4 = require("fs/promises");
415
- var import_node_path15 = require("path");
404
+ var import_node_fs12 = require("fs");
405
+ var import_promises5 = require("fs/promises");
406
+ var import_node_path17 = require("path");
416
407
  var prompts = __toESM(require("@clack/prompts"));
417
408
 
418
409
  // src/app.ts
@@ -823,8 +814,11 @@ async function buildGiriApp(config, options = {}) {
823
814
  const routes = await scanRoutes(paths.routesDir);
824
815
  const app = config.adapter.createApp();
825
816
  ensureGiriAliasResolver(paths.outDir);
826
- const { unregister } = await safeRegister();
827
- const unregisterAliasResolver = registerAliasResolver(config.alias, paths.cwd);
817
+ if (options.lazy && (!options.loaderRegistered || !options.aliasResolverRegistered)) {
818
+ throw new Error("Lazy route loading requires persistent loader and alias registrations.");
819
+ }
820
+ const loader = options.loaderRegistered ? void 0 : await safeRegister();
821
+ const unregisterAliasResolver = options.aliasResolverRegistered ? void 0 : registerAliasResolver(config.alias, paths.cwd);
828
822
  try {
829
823
  const dirty = options.dirty;
830
824
  const forceReload = dirty === void 0;
@@ -836,7 +830,7 @@ async function buildGiriApp(config, options = {}) {
836
830
  }
837
831
  return sharedCache.get(file);
838
832
  };
839
- for (const route of routes) {
833
+ const runtimeFor = (route) => {
840
834
  const routeModule = loadModule(route.file, isDirty(route.file));
841
835
  if (typeof routeModule.handle !== "function") {
842
836
  throw new Error(`${route.file} must export a named handle function.`);
@@ -845,7 +839,7 @@ async function buildGiriApp(config, options = {}) {
845
839
  (file) => normalizeMiddleware(loadShared(file).middleware, file)
846
840
  );
847
841
  const verbMiddleware = normalizeMiddleware(routeModule.middleware, route.file);
848
- config.adapter.register(app, {
842
+ return {
849
843
  method: route.method,
850
844
  path: route.path,
851
845
  handle: routeModule.handle,
@@ -853,19 +847,45 @@ async function buildGiriApp(config, options = {}) {
853
847
  input: routeInput(routeModule, route.file),
854
848
  services: options.services,
855
849
  cookieSecret: config.cookieSecret
850
+ };
851
+ };
852
+ for (const route of routes) {
853
+ if (!options.lazy) {
854
+ config.adapter.register(app, runtimeFor(route));
855
+ continue;
856
+ }
857
+ let runtime;
858
+ const getRuntime = () => {
859
+ runtime ??= runtimeFor(route);
860
+ return runtime;
861
+ };
862
+ config.adapter.register(app, {
863
+ method: route.method,
864
+ path: route.path,
865
+ get handle() {
866
+ return getRuntime().handle;
867
+ },
868
+ get middleware() {
869
+ return getRuntime().middleware;
870
+ },
871
+ get input() {
872
+ return getRuntime().input;
873
+ },
874
+ services: options.services,
875
+ cookieSecret: config.cookieSecret
856
876
  });
857
877
  }
858
878
  } finally {
859
- unregisterAliasResolver();
860
- unregister();
879
+ unregisterAliasResolver?.();
880
+ loader?.unregister();
861
881
  }
862
882
  return { app, routes, paths };
863
883
  }
864
884
 
865
885
  // src/generator/sync.ts
866
- var import_node_fs7 = require("fs");
867
- var import_promises3 = require("fs/promises");
868
- var import_node_path11 = require("path");
886
+ var import_node_fs8 = require("fs");
887
+ var import_promises4 = require("fs/promises");
888
+ var import_node_path12 = require("path");
869
889
 
870
890
  // src/generator/app-types.ts
871
891
  var import_node_fs4 = require("fs");
@@ -1812,6 +1832,103 @@ async function writeTsConfig(paths, config) {
1812
1832
  });
1813
1833
  }
1814
1834
 
1835
+ // src/generator/cache.ts
1836
+ var import_node_crypto = require("crypto");
1837
+ var import_node_fs7 = require("fs");
1838
+ var import_promises3 = require("fs/promises");
1839
+ var import_node_path11 = require("path");
1840
+ var import_tinyglobby2 = require("tinyglobby");
1841
+ var CACHE_VERSION = 1;
1842
+ var SYNC_CACHE_NAME = ".sync-cache.json";
1843
+ function stableConfig(config) {
1844
+ const alias = Object.entries(config.alias ?? {}).sort(([left], [right]) => left.localeCompare(right)).map(([key, value]) => [key, Array.isArray(value) ? [...value] : value]);
1845
+ return { alias, outDir: config.outDir ?? ".giri" };
1846
+ }
1847
+ async function syncFingerprint(config, paths) {
1848
+ const outRelative = slash((0, import_node_path11.relative)(paths.cwd, paths.outDir));
1849
+ const ignore = ["**/node_modules/**", "**/.git/**"];
1850
+ if (outRelative && !outRelative.startsWith("..")) {
1851
+ ignore.push(`${outRelative}/**`);
1852
+ }
1853
+ const files = await (0, import_tinyglobby2.glob)([
1854
+ "src/**/*.{ts,tsx,mts,cts,js,jsx,mjs,cjs,json}",
1855
+ "giri.config.{ts,js,mts,cts,mjs,cjs}",
1856
+ "tsconfig*.json",
1857
+ "package.json",
1858
+ "package-lock.json",
1859
+ "npm-shrinkwrap.json",
1860
+ "yarn.lock",
1861
+ "pnpm-lock.yaml",
1862
+ "bun.lock",
1863
+ "bun.lockb"
1864
+ ], {
1865
+ cwd: paths.cwd,
1866
+ absolute: false,
1867
+ onlyFiles: true,
1868
+ dot: true,
1869
+ ignore
1870
+ });
1871
+ const hash = (0, import_node_crypto.createHash)("sha256");
1872
+ hash.update(JSON.stringify(stableConfig(config)));
1873
+ for (const file of files.sort()) {
1874
+ hash.update("\0");
1875
+ hash.update(slash(file));
1876
+ hash.update("\0");
1877
+ hash.update(await (0, import_promises3.readFile)((0, import_node_path11.resolve)(paths.cwd, file)));
1878
+ }
1879
+ return hash.digest("hex");
1880
+ }
1881
+ function cachePath(paths) {
1882
+ return (0, import_node_path11.join)(paths.outDir, SYNC_CACHE_NAME);
1883
+ }
1884
+ function serializePath(paths, file) {
1885
+ return slash((0, import_node_path11.relative)(paths.cwd, file));
1886
+ }
1887
+ function deserializePath(paths, file) {
1888
+ return slash((0, import_node_path11.resolve)(paths.cwd, file.split("/").join(import_node_path11.sep)));
1889
+ }
1890
+ function serializeMap(paths, values) {
1891
+ return [...values].map(([file, value]) => [serializePath(paths, file), value]);
1892
+ }
1893
+ function deserializeMap(paths, values) {
1894
+ return new Map(values.map(([file, value]) => [deserializePath(paths, file), value]));
1895
+ }
1896
+ async function readSyncCache(paths, fingerprint) {
1897
+ const file = cachePath(paths);
1898
+ if (!(0, import_node_fs7.existsSync)(file)) {
1899
+ return void 0;
1900
+ }
1901
+ try {
1902
+ const cache = JSON.parse(await (0, import_promises3.readFile)(file, "utf8"));
1903
+ if (cache.version !== CACHE_VERSION || cache.fingerprint !== fingerprint) {
1904
+ return void 0;
1905
+ }
1906
+ return {
1907
+ responsesByFile: deserializeMap(paths, cache.data.responsesByFile),
1908
+ inputsByFile: deserializeMap(paths, cache.data.inputsByFile),
1909
+ securityByFile: deserializeMap(paths, cache.data.securityByFile),
1910
+ hiddenFiles: new Set(cache.data.hiddenFiles.map((entry) => deserializePath(paths, entry))),
1911
+ openapiByFile: deserializeMap(paths, cache.data.openapiByFile)
1912
+ };
1913
+ } catch {
1914
+ return void 0;
1915
+ }
1916
+ }
1917
+ async function writeSyncCache(paths, fingerprint, data) {
1918
+ const cache = {
1919
+ version: CACHE_VERSION,
1920
+ fingerprint,
1921
+ data: {
1922
+ responsesByFile: serializeMap(paths, data.responsesByFile),
1923
+ inputsByFile: serializeMap(paths, data.inputsByFile),
1924
+ securityByFile: serializeMap(paths, data.securityByFile),
1925
+ hiddenFiles: [...data.hiddenFiles].map((file) => serializePath(paths, file)),
1926
+ openapiByFile: serializeMap(paths, data.openapiByFile)
1927
+ }
1928
+ };
1929
+ await writeJson(cachePath(paths), cache);
1930
+ }
1931
+
1815
1932
  // src/generator/sync.ts
1816
1933
  async function typeFolders(paths, routes) {
1817
1934
  const verbsByDir = /* @__PURE__ */ new Map();
@@ -1838,57 +1955,17 @@ async function extractResponses(paths, routes) {
1838
1955
  try {
1839
1956
  const { createSchemaProgram: createSchemaProgram2, extractRouteResponses: extractRouteResponses2 } = await Promise.resolve().then(() => (init_schema(), schema_exports));
1840
1957
  const files = [...new Set(routes.map((route) => route.file))];
1841
- const appTypes = (0, import_node_path11.join)(paths.outDir, "types", "app.d.ts");
1842
- const roots = (0, import_node_fs7.existsSync)(appTypes) ? [...files, appTypes] : files;
1843
- const program = createSchemaProgram2(paths, roots, { lean: true });
1844
- const fallbackFiles = [];
1958
+ const appTypes = (0, import_node_path12.join)(paths.outDir, "types", "app.d.ts");
1959
+ const roots = (0, import_node_fs8.existsSync)(appTypes) ? [...files, appTypes] : files;
1960
+ const program = createSchemaProgram2(paths, roots);
1845
1961
  for (const file of files) {
1846
- const responses = extractRouteResponses2(program, file);
1847
- byFile.set(file, responses);
1848
- if (hasLooseResponseSchema(responses)) {
1849
- fallbackFiles.push(file);
1850
- }
1851
- }
1852
- if (fallbackFiles.length > 0) {
1853
- const fullProgram = createSchemaProgram2(
1854
- paths,
1855
- (0, import_node_fs7.existsSync)(appTypes) ? [...fallbackFiles, appTypes] : fallbackFiles
1856
- );
1857
- for (const file of fallbackFiles) {
1858
- byFile.set(file, extractRouteResponses2(fullProgram, file));
1859
- }
1962
+ byFile.set(file, extractRouteResponses2(program, file));
1860
1963
  }
1861
1964
  } catch (error) {
1862
1965
  console.warn(`giri: skipped response schema generation (${error.message}).`);
1863
1966
  }
1864
1967
  return byFile;
1865
1968
  }
1866
- function isLooseSchema(value) {
1867
- if (!value || typeof value !== "object") {
1868
- return false;
1869
- }
1870
- const schema = value;
1871
- const keys = Object.keys(schema);
1872
- if (keys.length === 0) {
1873
- return true;
1874
- }
1875
- if (typeof schema.$ref === "string") {
1876
- return false;
1877
- }
1878
- if (Array.isArray(schema.anyOf) && schema.anyOf.some(isLooseSchema)) {
1879
- return true;
1880
- }
1881
- if (schema.items && isLooseSchema(schema.items)) {
1882
- return true;
1883
- }
1884
- if (schema.properties && typeof schema.properties === "object") {
1885
- return Object.values(schema.properties).some(isLooseSchema);
1886
- }
1887
- return false;
1888
- }
1889
- function hasLooseResponseSchema(responses) {
1890
- return responses.responses.some((response) => isLooseSchema(response.schema));
1891
- }
1892
1969
  async function extractMeta(config, paths, routes) {
1893
1970
  const inputsByFile = /* @__PURE__ */ new Map();
1894
1971
  const securityByFile = /* @__PURE__ */ new Map();
@@ -1921,28 +1998,44 @@ async function extractMeta(config, paths, routes) {
1921
1998
  async function syncProject(config, options = {}) {
1922
1999
  const paths = resolveGiriPaths(config, options.cwd);
1923
2000
  assertSafeOutDir(paths);
1924
- const hadOutDir = (0, import_node_fs7.existsSync)(paths.outDir);
2001
+ const hadOutDir = (0, import_node_fs8.existsSync)(paths.outDir);
1925
2002
  const routes = await scanRoutes(paths.routesDir);
1926
2003
  const folders = await typeFolders(paths, routes);
1927
- await (0, import_promises3.mkdir)(paths.outDir, { recursive: true });
2004
+ const fingerprint = await syncFingerprint(config, paths);
2005
+ const cached = await readSyncCache(paths, fingerprint);
2006
+ const generatedFiles = [
2007
+ (0, import_node_path12.join)(paths.outDir, "tsconfig.json"),
2008
+ (0, import_node_path12.join)(paths.outDir, "manifest.json"),
2009
+ (0, import_node_path12.join)(paths.outDir, "openapi.json"),
2010
+ (0, import_node_path12.join)(paths.outDir, "routes.d.ts"),
2011
+ (0, import_node_path12.join)(paths.outDir, "types", "app.d.ts"),
2012
+ ...folders.map((folder) => typeFilePath(paths, folder.dir))
2013
+ ];
2014
+ if (cached && generatedFiles.every(import_node_fs8.existsSync)) {
2015
+ return { paths, routes, folders, data: cached };
2016
+ }
2017
+ await (0, import_promises4.mkdir)(paths.outDir, { recursive: true });
1928
2018
  await writeParamTypes(paths, folders);
1929
2019
  await writeRouteTypes(paths, routes);
1930
2020
  await writeAppTypes(paths);
1931
2021
  await writeTsConfig(paths, config);
1932
- const responsesByFile = await extractResponses(paths, routes);
1933
- const { inputsByFile, securityByFile, hiddenFiles, openapiByFile } = await extractMeta(config, paths, routes);
1934
- const data = { responsesByFile, inputsByFile, securityByFile, hiddenFiles, openapiByFile };
2022
+ const data = cached ?? {
2023
+ responsesByFile: await extractResponses(paths, routes),
2024
+ ...await extractMeta(config, paths, routes)
2025
+ };
1935
2026
  await writeManifest(paths, routes, data);
1936
2027
  await writeOpenApi(paths, routes, data);
2028
+ await writeSyncCache(paths, fingerprint, data);
1937
2029
  if (hadOutDir) {
1938
2030
  await pruneDir(
1939
2031
  paths.outDir,
1940
2032
  /* @__PURE__ */ new Set([
1941
- (0, import_node_path11.join)(paths.outDir, "tsconfig.json"),
1942
- (0, import_node_path11.join)(paths.outDir, "manifest.json"),
1943
- (0, import_node_path11.join)(paths.outDir, "openapi.json"),
1944
- (0, import_node_path11.join)(paths.outDir, "routes.d.ts"),
1945
- (0, import_node_path11.join)(paths.outDir, "types", "app.d.ts"),
2033
+ (0, import_node_path12.join)(paths.outDir, "tsconfig.json"),
2034
+ (0, import_node_path12.join)(paths.outDir, "manifest.json"),
2035
+ (0, import_node_path12.join)(paths.outDir, "openapi.json"),
2036
+ (0, import_node_path12.join)(paths.outDir, "routes.d.ts"),
2037
+ (0, import_node_path12.join)(paths.outDir, SYNC_CACHE_NAME),
2038
+ (0, import_node_path12.join)(paths.outDir, "types", "app.d.ts"),
1946
2039
  ...folders.map((folder) => typeFilePath(paths, folder.dir))
1947
2040
  ])
1948
2041
  );
@@ -1951,43 +2044,122 @@ async function syncProject(config, options = {}) {
1951
2044
  }
1952
2045
 
1953
2046
  // src/generator/watch.ts
1954
- var import_node_fs8 = require("fs");
1955
- var import_node_path13 = require("path");
2047
+ var import_node_fs10 = require("fs");
2048
+ var import_node_path15 = require("path");
1956
2049
 
1957
- // src/loader/module-loader.ts
1958
- var import_node_path12 = require("path");
1959
- var toSlash = (path) => path.split(import_node_path12.sep).join("/");
1960
- var isProjectModule = (id, root) => {
1961
- return id.startsWith(root) && !id.includes(`${import_node_path12.sep}node_modules${import_node_path12.sep}`) && !id.includes(`${import_node_path12.sep}.giri${import_node_path12.sep}`);
1962
- };
1963
- var buildModuleGraph = (cwd) => {
1964
- const root = (0, import_node_path12.resolve)(cwd) + import_node_path12.sep;
2050
+ // src/loader/import-graph.ts
2051
+ var import_node_fs9 = require("fs");
2052
+ var import_node_path13 = require("path");
2053
+ var import_typescript5 = __toESM(require("typescript"));
2054
+ var import_tinyglobby3 = require("tinyglobby");
2055
+ var RESOLVE_EXTS = [".ts", ".tsx", ".mts", ".cts", ".js", ".jsx", ".mjs", ".cjs"];
2056
+ var JS_EXT = /\.(?:c|m)?jsx?$/;
2057
+ var toSlash = (path) => path.split(import_node_path13.sep).join("/");
2058
+ function probeFile(base) {
2059
+ if ((0, import_node_fs9.existsSync)(base) && (0, import_node_fs9.statSync)(base).isFile()) {
2060
+ return base;
2061
+ }
2062
+ for (const ext of RESOLVE_EXTS) {
2063
+ const candidate = base + ext;
2064
+ if ((0, import_node_fs9.existsSync)(candidate)) {
2065
+ return candidate;
2066
+ }
2067
+ }
2068
+ for (const ext of RESOLVE_EXTS) {
2069
+ const candidate = (0, import_node_path13.join)(base, `index${ext}`);
2070
+ if ((0, import_node_fs9.existsSync)(candidate)) {
2071
+ return candidate;
2072
+ }
2073
+ }
2074
+ return void 0;
2075
+ }
2076
+ function resolveSpecifier(specifier, fromFile, alias, cwd) {
2077
+ let target;
2078
+ if (specifier.startsWith(".")) {
2079
+ target = (0, import_node_path13.resolve)((0, import_node_path13.dirname)(fromFile), specifier);
2080
+ } else {
2081
+ const aliased = resolveAliasRequest(specifier, alias, cwd);
2082
+ if (aliased === void 0) {
2083
+ return void 0;
2084
+ }
2085
+ target = aliased;
2086
+ }
2087
+ const resolved = probeFile(target);
2088
+ if (resolved) {
2089
+ return resolved;
2090
+ }
2091
+ if (JS_EXT.test(target)) {
2092
+ return probeFile(target.replace(JS_EXT, ""));
2093
+ }
2094
+ return void 0;
2095
+ }
2096
+ function importSpecifiers(file) {
2097
+ let source;
2098
+ try {
2099
+ source = import_typescript5.default.createSourceFile(file, (0, import_node_fs9.readFileSync)(file, "utf8"), import_typescript5.default.ScriptTarget.Latest, false);
2100
+ } catch {
2101
+ return [];
2102
+ }
2103
+ const specifiers = [];
2104
+ const visit = (node) => {
2105
+ if ((import_typescript5.default.isImportDeclaration(node) || import_typescript5.default.isExportDeclaration(node)) && node.moduleSpecifier && import_typescript5.default.isStringLiteral(node.moduleSpecifier)) {
2106
+ specifiers.push(node.moduleSpecifier.text);
2107
+ } else if (import_typescript5.default.isImportEqualsDeclaration(node) && import_typescript5.default.isExternalModuleReference(node.moduleReference) && import_typescript5.default.isStringLiteralLike(node.moduleReference.expression)) {
2108
+ specifiers.push(node.moduleReference.expression.text);
2109
+ } else if (import_typescript5.default.isCallExpression(node)) {
2110
+ const isRequire = import_typescript5.default.isIdentifier(node.expression) && node.expression.text === "require";
2111
+ const isDynamicImport = node.expression.kind === import_typescript5.default.SyntaxKind.ImportKeyword;
2112
+ const [first] = node.arguments;
2113
+ if ((isRequire || isDynamicImport) && first && import_typescript5.default.isStringLiteralLike(first)) {
2114
+ specifiers.push(first.text);
2115
+ }
2116
+ }
2117
+ import_typescript5.default.forEachChild(node, visit);
2118
+ };
2119
+ visit(source);
2120
+ return specifiers;
2121
+ }
2122
+ async function buildImportGraph(config, cwd) {
2123
+ const root = (0, import_node_path13.resolve)(cwd);
2124
+ const outDir = (0, import_node_path13.resolve)(root, config.outDir ?? ".giri") + import_node_path13.sep;
2125
+ const outRel = (0, import_node_path13.relative)(root, outDir).split(import_node_path13.sep).join("/").replace(/\/$/, "");
2126
+ const files = await (0, import_tinyglobby3.glob)("**/*.{ts,tsx,mts,cts,js,jsx,mjs,cjs}", {
2127
+ cwd: root,
2128
+ absolute: true,
2129
+ onlyFiles: true,
2130
+ ignore: ["**/node_modules/**", "**/.git/**", outRel ? `${outRel}/**` : ".giri/**"]
2131
+ });
1965
2132
  const importers = /* @__PURE__ */ new Map();
1966
2133
  const nodes = /* @__PURE__ */ new Set();
1967
- for (const id of Object.keys(require.cache)) {
1968
- if (!isProjectModule(id, root)) {
1969
- continue;
1970
- }
1971
- const mod = require.cache[id];
1972
- if (!mod) {
2134
+ for (const file of files) {
2135
+ if (file.startsWith(outDir)) {
1973
2136
  continue;
1974
2137
  }
1975
- nodes.add(toSlash(id));
1976
- for (const child of mod.children) {
1977
- if (!isProjectModule(child.id, root)) {
2138
+ for (const specifier of importSpecifiers(file)) {
2139
+ const dep = resolveSpecifier(specifier, file, config.alias, root);
2140
+ if (!dep || dep.startsWith(outDir)) {
1978
2141
  continue;
1979
2142
  }
1980
- nodes.add(toSlash(child.id));
1981
- const dep = toSlash(child.id);
1982
- let set = importers.get(dep);
2143
+ const from = toSlash(file);
2144
+ const to = toSlash(dep);
2145
+ nodes.add(from);
2146
+ nodes.add(to);
2147
+ let set = importers.get(to);
1983
2148
  if (!set) {
1984
2149
  set = /* @__PURE__ */ new Set();
1985
- importers.set(dep, set);
2150
+ importers.set(to, set);
1986
2151
  }
1987
- set.add(toSlash(id));
2152
+ set.add(from);
1988
2153
  }
1989
2154
  }
1990
2155
  return { importers, nodes };
2156
+ }
2157
+
2158
+ // src/loader/module-loader.ts
2159
+ var import_node_path14 = require("path");
2160
+ var toSlash2 = (path) => path.split(import_node_path14.sep).join("/");
2161
+ var isProjectModule = (id, root) => {
2162
+ return id.startsWith(root) && !id.includes(`${import_node_path14.sep}node_modules${import_node_path14.sep}`) && !id.includes(`${import_node_path14.sep}.giri${import_node_path14.sep}`);
1991
2163
  };
1992
2164
  var collectDependents = (graph, start) => {
1993
2165
  const out = /* @__PURE__ */ new Set([start]);
@@ -2005,13 +2177,13 @@ var collectDependents = (graph, start) => {
2005
2177
  };
2006
2178
  var purgeModules = (files) => {
2007
2179
  for (const id of Object.keys(require.cache)) {
2008
- if (files.has(toSlash(id))) {
2180
+ if (files.has(toSlash2(id))) {
2009
2181
  delete require.cache[id];
2010
2182
  }
2011
2183
  }
2012
2184
  };
2013
2185
  var purgeProjectModules = (cwd) => {
2014
- const root = (0, import_node_path12.resolve)(cwd) + import_node_path12.sep;
2186
+ const root = (0, import_node_path14.resolve)(cwd) + import_node_path14.sep;
2015
2187
  for (const id of Object.keys(require.cache)) {
2016
2188
  if (isProjectModule(id, root)) {
2017
2189
  delete require.cache[id];
@@ -2019,9 +2191,9 @@ var purgeProjectModules = (cwd) => {
2019
2191
  }
2020
2192
  };
2021
2193
  var purgeGeneratedModules = (outDir) => {
2022
- const root = (0, import_node_path12.resolve)(outDir) + import_node_path12.sep;
2194
+ const root = (0, import_node_path14.resolve)(outDir) + import_node_path14.sep;
2023
2195
  for (const id of Object.keys(require.cache)) {
2024
- if ((0, import_node_path12.resolve)(id).startsWith(root)) {
2196
+ if ((0, import_node_path14.resolve)(id).startsWith(root)) {
2025
2197
  delete require.cache[id];
2026
2198
  }
2027
2199
  }
@@ -2048,8 +2220,8 @@ function createWatchUpdater(config, initial) {
2048
2220
  const key = route.file;
2049
2221
  try {
2050
2222
  const { createSchemaProgram: createSchemaProgram2, extractRouteResponses: extractRouteResponses2 } = await Promise.resolve().then(() => (init_schema(), schema_exports));
2051
- const appTypes = (0, import_node_path13.join)(paths.outDir, "types", "app.d.ts");
2052
- const program = createSchemaProgram2(paths, (0, import_node_fs8.existsSync)(appTypes) ? [key, appTypes] : [key]);
2223
+ const appTypes = (0, import_node_path15.join)(paths.outDir, "types", "app.d.ts");
2224
+ const program = createSchemaProgram2(paths, (0, import_node_fs10.existsSync)(appTypes) ? [key, appTypes] : [key]);
2053
2225
  data.responsesByFile.set(key, extractRouteResponses2(program, key));
2054
2226
  } catch {
2055
2227
  }
@@ -2080,15 +2252,15 @@ function createWatchUpdater(config, initial) {
2080
2252
  if (!filename) {
2081
2253
  return fullResync();
2082
2254
  }
2083
- const abs = (0, import_node_path13.resolve)((0, import_node_path13.dirname)(paths.routesDir), filename);
2255
+ const abs = (0, import_node_path15.resolve)((0, import_node_path15.dirname)(paths.routesDir), filename);
2084
2256
  const file = slash(abs);
2085
- if (!(0, import_node_fs8.existsSync)(abs)) {
2257
+ if (!(0, import_node_fs10.existsSync)(abs)) {
2086
2258
  return fullResync();
2087
2259
  }
2088
- if ((0, import_node_fs8.statSync)(abs).isDirectory()) {
2260
+ if ((0, import_node_fs10.statSync)(abs).isDirectory()) {
2089
2261
  return "skip";
2090
2262
  }
2091
- const graph = buildModuleGraph(paths.cwd);
2263
+ const graph = await buildImportGraph(config, paths.cwd);
2092
2264
  const isRoute = routes.some((candidate) => slash(candidate.file) === file);
2093
2265
  if (!graph.nodes.has(file) && !isRoute) {
2094
2266
  return fullResync();
@@ -2103,6 +2275,7 @@ function createWatchUpdater(config, initial) {
2103
2275
  }
2104
2276
  await writeManifest(paths, routes, data);
2105
2277
  await writeOpenApi(paths, routes, data);
2278
+ await writeSyncCache(paths, await syncFingerprint(config, paths), data);
2106
2279
  purgeGeneratedModules(paths.outDir);
2107
2280
  return "incremental";
2108
2281
  }
@@ -2110,20 +2283,20 @@ function createWatchUpdater(config, initial) {
2110
2283
  }
2111
2284
 
2112
2285
  // src/lifecycle.ts
2113
- var import_node_fs9 = require("fs");
2114
- var import_node_path14 = require("path");
2286
+ var import_node_fs11 = require("fs");
2287
+ var import_node_path16 = require("path");
2115
2288
  var MAIN_EXTENSIONS2 = ["ts", "tsx", "mts", "cts", "js", "jsx", "mjs", "cjs"];
2116
2289
  function resolveMainFile(cwd) {
2117
2290
  for (const ext of MAIN_EXTENSIONS2) {
2118
- const file = (0, import_node_path14.join)(cwd, "src", `main.${ext}`);
2119
- if ((0, import_node_fs9.existsSync)(file)) {
2291
+ const file = (0, import_node_path16.join)(cwd, "src", `main.${ext}`);
2292
+ if ((0, import_node_fs11.existsSync)(file)) {
2120
2293
  return file;
2121
2294
  }
2122
2295
  }
2123
2296
  return void 0;
2124
2297
  }
2125
2298
  async function loadLifecycle(cwd = process.cwd()) {
2126
- const file = resolveMainFile((0, import_node_path14.resolve)(cwd));
2299
+ const file = resolveMainFile((0, import_node_path16.resolve)(cwd));
2127
2300
  if (!file) {
2128
2301
  return {};
2129
2302
  }
@@ -2281,25 +2454,25 @@ function parseFlags(args) {
2281
2454
  return flags;
2282
2455
  }
2283
2456
  async function ensureGitignore(cwd) {
2284
- const file = (0, import_node_path15.join)(cwd, ".gitignore");
2457
+ const file = (0, import_node_path17.join)(cwd, ".gitignore");
2285
2458
  const entry = ".giri";
2286
- if (!(0, import_node_fs10.existsSync)(file)) {
2287
- await (0, import_promises4.writeFile)(file, `${entry}
2459
+ if (!(0, import_node_fs12.existsSync)(file)) {
2460
+ await (0, import_promises5.writeFile)(file, `${entry}
2288
2461
  `);
2289
2462
  return;
2290
2463
  }
2291
- const content = await (0, import_promises4.readFile)(file, "utf8");
2464
+ const content = await (0, import_promises5.readFile)(file, "utf8");
2292
2465
  if (!content.split(/\r?\n/).includes(entry)) {
2293
- await (0, import_promises4.appendFile)(file, `${content.endsWith("\n") ? "" : "\n"}${entry}
2466
+ await (0, import_promises5.appendFile)(file, `${content.endsWith("\n") ? "" : "\n"}${entry}
2294
2467
  `);
2295
2468
  }
2296
2469
  }
2297
2470
  async function ensureTsConfig(cwd) {
2298
- const file = (0, import_node_path15.join)(cwd, "tsconfig.json");
2299
- if ((0, import_node_fs10.existsSync)(file)) {
2471
+ const file = (0, import_node_path17.join)(cwd, "tsconfig.json");
2472
+ if ((0, import_node_fs12.existsSync)(file)) {
2300
2473
  return;
2301
2474
  }
2302
- await (0, import_promises4.writeFile)(
2475
+ await (0, import_promises5.writeFile)(
2303
2476
  file,
2304
2477
  `${JSON.stringify(
2305
2478
  {
@@ -2325,7 +2498,7 @@ async function ensureTsConfig(cwd) {
2325
2498
  async function missingDeps(cwd, candidates) {
2326
2499
  let pkg = {};
2327
2500
  try {
2328
- pkg = JSON.parse(await (0, import_promises4.readFile)((0, import_node_path15.join)(cwd, "package.json"), "utf8"));
2501
+ pkg = JSON.parse(await (0, import_promises5.readFile)((0, import_node_path17.join)(cwd, "package.json"), "utf8"));
2329
2502
  } catch {
2330
2503
  }
2331
2504
  const present = /* @__PURE__ */ new Set();
@@ -2394,7 +2567,7 @@ async function selectAdapter(interactive) {
2394
2567
  return ADAPTERS.find((adapter) => adapter.value === picked) ?? null;
2395
2568
  }
2396
2569
  async function initProject(cwd, flags) {
2397
- if (!(0, import_node_fs10.existsSync)((0, import_node_path15.join)(cwd, "package.json"))) {
2570
+ if (!(0, import_node_fs12.existsSync)((0, import_node_path17.join)(cwd, "package.json"))) {
2398
2571
  throw new Error(
2399
2572
  "No package.json found. Run `giri init` inside an existing project - set one up first (e.g. `npm init -y` and install typescript), then re-run."
2400
2573
  );
@@ -2419,14 +2592,14 @@ async function initProject(cwd, flags) {
2419
2592
  prompts.cancel(`The ${adapter.label} adapter isn't available yet - only Hono ships today.`);
2420
2593
  return;
2421
2594
  }
2422
- const configPath = (0, import_node_path15.join)(cwd, "giri.config.ts");
2423
- if (!(0, import_node_fs10.existsSync)(configPath)) {
2424
- await (0, import_promises4.writeFile)(configPath, configSource(adapter));
2595
+ const configPath = (0, import_node_path17.join)(cwd, "giri.config.ts");
2596
+ if (!(0, import_node_fs12.existsSync)(configPath)) {
2597
+ await (0, import_promises5.writeFile)(configPath, configSource(adapter));
2425
2598
  }
2426
- const routePath = (0, import_node_path15.join)(cwd, "src", "routes", "+get.ts");
2427
- if (!(0, import_node_fs10.existsSync)(routePath)) {
2428
- await (0, import_promises4.mkdir)((0, import_node_path15.join)(cwd, "src", "routes"), { recursive: true });
2429
- await (0, import_promises4.writeFile)(
2599
+ const routePath = (0, import_node_path17.join)(cwd, "src", "routes", "+get.ts");
2600
+ if (!(0, import_node_fs12.existsSync)(routePath)) {
2601
+ await (0, import_promises5.mkdir)((0, import_node_path17.join)(cwd, "src", "routes"), { recursive: true });
2602
+ await (0, import_promises5.writeFile)(
2430
2603
  routePath,
2431
2604
  [
2432
2605
  'import type { Handle } from "@boon4681/giri";',
@@ -2497,21 +2670,30 @@ async function serveProject(config, flags) {
2497
2670
  const { watch } = await import("chokidar");
2498
2671
  let stop;
2499
2672
  const boot = async (cfg) => {
2500
- const initial = await syncProject(cfg);
2501
- log2.success(
2502
- `synced ${initial.routes.length} route${initial.routes.length === 1 ? "" : "s"} ${muted(`at ${initial.paths.outDir}`)}`,
2503
- "sync"
2504
- );
2505
2673
  const closers = [];
2506
- closers.push(registerAliasResolver(cfg.alias, initial.paths.cwd));
2674
+ closers.push(registerAliasResolver(cfg.alias, (0, import_node_path17.resolve)(process.cwd())));
2507
2675
  const lifecycle = await loadLifecycle();
2508
- const services = await runInit(lifecycle);
2509
- let current = await buildGiriApp(cfg, { services });
2676
+ const sync = syncProject(cfg).then((initial2) => {
2677
+ log2.success(
2678
+ `synced ${initial2.routes.length} route${initial2.routes.length === 1 ? "" : "s"} ${muted(`at ${initial2.paths.outDir}`)}`,
2679
+ "sync"
2680
+ );
2681
+ return initial2;
2682
+ });
2683
+ const [initial, services] = await Promise.all([sync, runInit(lifecycle)]);
2684
+ const loader = await safeRegister();
2685
+ closers.push(loader.unregister);
2686
+ let current = await buildGiriApp(cfg, {
2687
+ services,
2688
+ lazy: true,
2689
+ loaderRegistered: true,
2690
+ aliasResolverRegistered: true
2691
+ });
2510
2692
  const port = flags.port ?? cfg.server?.port ?? 3e3;
2511
2693
  const hostname = flags.hostname ?? cfg.server?.hostname;
2512
2694
  if (flags.watch) {
2513
- const srcDir = (0, import_node_path15.resolve)(current.paths.routesDir, "..");
2514
- if ((0, import_node_fs10.existsSync)(srcDir)) {
2695
+ const srcDir = (0, import_node_path17.resolve)(current.paths.routesDir, "..");
2696
+ if ((0, import_node_fs12.existsSync)(srcDir)) {
2515
2697
  let timer;
2516
2698
  let syncing = false;
2517
2699
  const changed = /* @__PURE__ */ new Set();
@@ -2541,7 +2723,7 @@ async function serveProject(config, flags) {
2541
2723
  }
2542
2724
  rebuild = true;
2543
2725
  if (name) {
2544
- dirtySet.add((0, import_node_path15.resolve)(srcDir, name));
2726
+ dirtySet.add((0, import_node_path17.resolve)(srcDir, name));
2545
2727
  }
2546
2728
  const rel = name ? `src/${name.replace(/\\/g, "/")}` : "src";
2547
2729
  log2.change(outcome === "full" ? "sync" : "update", rel, bump(rel));
@@ -2553,7 +2735,10 @@ async function serveProject(config, flags) {
2553
2735
  if (rebuild) {
2554
2736
  current = await buildGiriApp(cfg, {
2555
2737
  services,
2556
- dirty: fullSync ? void 0 : dirtySet
2738
+ dirty: fullSync ? void 0 : dirtySet,
2739
+ lazy: true,
2740
+ loaderRegistered: true,
2741
+ aliasResolverRegistered: true
2557
2742
  });
2558
2743
  }
2559
2744
  }
@@ -2566,9 +2751,9 @@ async function serveProject(config, flags) {
2566
2751
  void flush();
2567
2752
  }
2568
2753
  };
2569
- const watcher = watch(srcDir, { persistent: true });
2754
+ const watcher = watch(srcDir, { persistent: true, ignoreInitial: true });
2570
2755
  const onFileChange = (filename) => {
2571
- changed.add(filename);
2756
+ changed.add((0, import_node_path17.isAbsolute)(filename) ? (0, import_node_path17.relative)(srcDir, filename) : filename);
2572
2757
  clearTimeout(timer);
2573
2758
  timer = setTimeout(() => void flush(), 150);
2574
2759
  };
@@ -2599,11 +2784,11 @@ async function serveProject(config, flags) {
2599
2784
  };
2600
2785
  };
2601
2786
  await boot(config);
2602
- const configPath = flags.watch ? findConfigPath((0, import_node_path15.resolve)(process.cwd())) : void 0;
2787
+ const configPath = flags.watch ? findConfigPath((0, import_node_path17.resolve)(process.cwd())) : void 0;
2603
2788
  if (configPath) {
2604
2789
  let timer;
2605
2790
  let restarting = false;
2606
- const configName = (0, import_node_path15.basename)(configPath);
2791
+ const configName = (0, import_node_path17.basename)(configPath);
2607
2792
  const restart = async () => {
2608
2793
  if (restarting) {
2609
2794
  return;
@@ -2622,12 +2807,10 @@ async function serveProject(config, flags) {
2622
2807
  restarting = false;
2623
2808
  }
2624
2809
  };
2625
- const configWatcher = watch((0, import_node_path15.dirname)(configPath), { persistent: true });
2626
- configWatcher.on("all", (_event, filename) => {
2627
- if (filename && (0, import_node_path15.basename)(filename.toString()) === configName) {
2628
- clearTimeout(timer);
2629
- timer = setTimeout(() => void restart(), 150);
2630
- }
2810
+ const configWatcher = watch(configPath, { persistent: true, ignoreInitial: true });
2811
+ configWatcher.on("all", () => {
2812
+ clearTimeout(timer);
2813
+ timer = setTimeout(() => void restart(), 150);
2631
2814
  });
2632
2815
  }
2633
2816
  registerShutdown(() => stop?.());
@@ -2653,7 +2836,7 @@ function registerShutdown(cleanup) {
2653
2836
  }
2654
2837
  async function main() {
2655
2838
  const [command = "help", ...args] = process.argv.slice(2);
2656
- const cwd = (0, import_node_path15.resolve)(process.cwd());
2839
+ const cwd = (0, import_node_path17.resolve)(process.cwd());
2657
2840
  if (command === "help" || command === "--help" || command === "-h") {
2658
2841
  help();
2659
2842
  return;