veryfront 0.1.83 → 0.1.85

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.
Files changed (56) hide show
  1. package/esm/cli/commands/knowledge/command.d.ts +10 -13
  2. package/esm/cli/commands/knowledge/command.d.ts.map +1 -1
  3. package/esm/cli/commands/knowledge/command.js +294 -110
  4. package/esm/cli/commands/knowledge/parser-source.d.ts.map +1 -1
  5. package/esm/cli/commands/knowledge/parser-source.js +52 -0
  6. package/esm/cli/commands/knowledge/result.d.ts +54 -0
  7. package/esm/cli/commands/knowledge/result.d.ts.map +1 -0
  8. package/esm/cli/commands/knowledge/result.js +22 -0
  9. package/esm/cli/commands/knowledge/source-policy.d.ts +11 -0
  10. package/esm/cli/commands/knowledge/source-policy.d.ts.map +1 -0
  11. package/esm/cli/commands/knowledge/source-policy.js +135 -0
  12. package/esm/deno.js +1 -1
  13. package/esm/src/jobs/index.d.ts +1 -1
  14. package/esm/src/jobs/index.d.ts.map +1 -1
  15. package/esm/src/jobs/index.js +1 -1
  16. package/esm/src/jobs/schemas.d.ts +1104 -185
  17. package/esm/src/jobs/schemas.d.ts.map +1 -1
  18. package/esm/src/jobs/schemas.js +81 -8
  19. package/esm/src/proxy/error-response.d.ts +7 -0
  20. package/esm/src/proxy/error-response.d.ts.map +1 -0
  21. package/esm/src/proxy/error-response.js +26 -0
  22. package/esm/src/proxy/handler.d.ts.map +1 -1
  23. package/esm/src/proxy/handler.js +25 -0
  24. package/esm/src/proxy/main.js +2 -23
  25. package/esm/src/rendering/orchestrator/css-candidate-manifest.d.ts +11 -0
  26. package/esm/src/rendering/orchestrator/css-candidate-manifest.d.ts.map +1 -1
  27. package/esm/src/rendering/orchestrator/css-candidate-manifest.js +23 -12
  28. package/esm/src/server/handlers/dev/styles-candidate-scanner.d.ts.map +1 -1
  29. package/esm/src/server/handlers/dev/styles-candidate-scanner.js +25 -8
  30. package/esm/src/server/handlers/dev/styles-css.handler.d.ts +1 -0
  31. package/esm/src/server/handlers/dev/styles-css.handler.d.ts.map +1 -1
  32. package/esm/src/server/handlers/dev/styles-css.handler.js +15 -5
  33. package/esm/src/transforms/esm/http-cache-helpers.d.ts.map +1 -1
  34. package/esm/src/transforms/esm/http-cache-helpers.js +7 -1
  35. package/esm/src/transforms/import-rewriter/strategies/bare-strategy.d.ts.map +1 -1
  36. package/esm/src/transforms/import-rewriter/strategies/bare-strategy.js +11 -8
  37. package/esm/src/transforms/shared/package-specifier.d.ts +7 -0
  38. package/esm/src/transforms/shared/package-specifier.d.ts.map +1 -0
  39. package/esm/src/transforms/shared/package-specifier.js +19 -0
  40. package/package.json +1 -1
  41. package/src/cli/commands/knowledge/command.ts +375 -139
  42. package/src/cli/commands/knowledge/parser-source.ts +52 -0
  43. package/src/cli/commands/knowledge/result.ts +88 -0
  44. package/src/cli/commands/knowledge/source-policy.ts +164 -0
  45. package/src/deno.js +1 -1
  46. package/src/src/jobs/index.ts +16 -0
  47. package/src/src/jobs/schemas.ts +105 -8
  48. package/src/src/proxy/error-response.ts +33 -0
  49. package/src/src/proxy/handler.ts +43 -0
  50. package/src/src/proxy/main.ts +2 -27
  51. package/src/src/rendering/orchestrator/css-candidate-manifest.ts +40 -14
  52. package/src/src/server/handlers/dev/styles-candidate-scanner.ts +37 -11
  53. package/src/src/server/handlers/dev/styles-css.handler.ts +25 -4
  54. package/src/src/transforms/esm/http-cache-helpers.ts +12 -1
  55. package/src/src/transforms/import-rewriter/strategies/bare-strategy.ts +11 -12
  56. package/src/src/transforms/shared/package-specifier.ts +29 -0
@@ -37,12 +37,12 @@ import {
37
37
  withSpan,
38
38
  } from "./tracing.js";
39
39
  import { proxyLogger, runWithProxyRequestContext } from "./logger.js";
40
- import { ErrorPages } from "../server/utils/error-html.js";
41
40
  import { RendererRouter } from "./renderer-router.js";
42
41
  import { ServerResolver } from "./server-resolver.js";
43
42
  import { parseProjectDomain } from "../server/utils/domain-parser.js";
44
43
  import { exit, getEnv, onSignal } from "../platform/compat/process.js";
45
44
  import { createHttpServer, upgradeWebSocket } from "../platform/compat/http/index.js";
45
+ import { createProxyErrorResponse, jsonErrorResponse } from "./error-response.js";
46
46
 
47
47
  function getLocalProjects(): Record<string, string> {
48
48
  const raw = getEnv("LOCAL_PROJECTS");
@@ -274,13 +274,6 @@ function handleWebSocketUpgrade(req: dntShim.Request): dntShim.Response {
274
274
  return response;
275
275
  }
276
276
 
277
- function jsonErrorResponse(status: number, body: Record<string, unknown>): dntShim.Response {
278
- return new dntShim.Response(JSON.stringify(body), {
279
- status,
280
- headers: { "Content-Type": "application/json" },
281
- });
282
- }
283
-
284
277
  function forwardToServer(req: dntShim.Request): Promise<dntShim.Response> {
285
278
  const startTime = performance.now();
286
279
  const url = new URL(req.url);
@@ -311,25 +304,7 @@ function forwardToServer(req: dntShim.Request): Promise<dntShim.Response> {
311
304
  const logLevel = ctx.error.status < 500 ? "warn" : "error";
312
305
  proxyLogger[logLevel](`${ctx.error.status} ${req.method} ${url.pathname}`, { ms });
313
306
  endSpan(spanInfo?.span, ctx.error.status);
314
-
315
- if (ctx.error.redirectUrl) {
316
- return new dntShim.Response(null, {
317
- status: 302,
318
- headers: { Location: ctx.error.redirectUrl },
319
- });
320
- }
321
-
322
- if (ctx.error.slug === "release-not-found") {
323
- return new dntShim.Response(ErrorPages.notFound(), {
324
- status: 404,
325
- headers: { "Content-Type": "text/html; charset=utf-8" },
326
- });
327
- }
328
-
329
- return jsonErrorResponse(ctx.error.status, {
330
- error: ctx.error.message,
331
- status: ctx.error.status,
332
- });
307
+ return createProxyErrorResponse(ctx.error);
333
308
  }
334
309
 
335
310
  const reqLogger = proxyLogger.child({
@@ -23,6 +23,14 @@ interface RouteCandidateOptions {
23
23
  developmentMode: boolean;
24
24
  }
25
25
 
26
+ interface ProjectCandidateOptions {
27
+ projectScope: string;
28
+ projectVersion: string;
29
+ projectDir: string;
30
+ files: SourceFileLike[];
31
+ developmentMode: boolean;
32
+ }
33
+
26
34
  const logger = rendererLogger.component("css-candidate-manifest");
27
35
  const SOURCE_EXTENSIONS = [".tsx", ".jsx", ".mdx", ".ts", ".js"];
28
36
  const DEV_MANIFEST_TTL_MS = 2_000;
@@ -90,6 +98,29 @@ function buildCandidateManifest(files: SourceFileLike[], projectDir: string): Ca
90
98
  return { fileCandidates, allCandidates, builtAt: Date.now() };
91
99
  }
92
100
 
101
+ function getOrBuildManifest(
102
+ options: Pick<
103
+ ProjectCandidateOptions,
104
+ "projectScope" | "projectVersion" | "projectDir" | "files" | "developmentMode"
105
+ >,
106
+ ): CandidateManifest {
107
+ const manifestKey = buildManifestCacheKey(options.projectScope, options.projectVersion);
108
+ const existingManifest = manifestCache.get(manifestKey);
109
+ const manifest = shouldRebuildManifest(existingManifest, options.developmentMode)
110
+ ? buildCandidateManifest(options.files, options.projectDir)
111
+ : existingManifest!;
112
+
113
+ if (manifest !== existingManifest) {
114
+ manifestCache.set(manifestKey, manifest);
115
+
116
+ for (const key of routeCandidateCache.keys()) {
117
+ if (key.startsWith(`${manifestKey}:`)) routeCandidateCache.delete(key);
118
+ }
119
+ }
120
+
121
+ return manifest;
122
+ }
123
+
93
124
  function addCandidatesForPath(
94
125
  target: Set<string>,
95
126
  manifest: CandidateManifest,
@@ -109,20 +140,7 @@ function addCandidatesForPath(
109
140
  */
110
141
  export function getRouteCandidates(options: RouteCandidateOptions): Set<string> {
111
142
  const manifestKey = buildManifestCacheKey(options.projectScope, options.projectVersion);
112
- const existingManifest = manifestCache.get(manifestKey);
113
- const manifest = shouldRebuildManifest(existingManifest, options.developmentMode)
114
- ? buildCandidateManifest(options.files, options.projectDir)
115
- : existingManifest!;
116
-
117
- if (manifest !== existingManifest) {
118
- manifestCache.set(manifestKey, manifest);
119
-
120
- // Clear route subsets when project-level file manifest is rebuilt.
121
- for (const key of routeCandidateCache.keys()) {
122
- if (key.startsWith(`${manifestKey}:`)) routeCandidateCache.delete(key);
123
- }
124
- }
125
-
143
+ const manifest = getOrBuildManifest(options);
126
144
  const routeCacheKey = `${manifestKey}:${options.routeKey}`;
127
145
  const cachedRoute = routeCandidateCache.get(routeCacheKey);
128
146
  if (cachedRoute) return new Set(cachedRoute);
@@ -156,6 +174,14 @@ export function getRouteCandidates(options: RouteCandidateOptions): Set<string>
156
174
  return new Set(routeCandidates);
157
175
  }
158
176
 
177
+ /**
178
+ * Resolve full-project Tailwind candidates from a precomputed per-project manifest.
179
+ */
180
+ export function getProjectCandidates(options: ProjectCandidateOptions): Set<string> {
181
+ const manifest = getOrBuildManifest(options);
182
+ return new Set(manifest.allCandidates);
183
+ }
184
+
159
185
  /**
160
186
  * Invalidate cached candidate manifests for one project scope (or all scopes).
161
187
  */
@@ -12,6 +12,8 @@ import { extractCandidates } from "../../../html/styles-builder/tailwind-compile
12
12
  import { serverLogger } from "../../../utils/index.js";
13
13
  import { createFileSystem } from "../../../platform/compat/fs.js";
14
14
  import { join } from "../../../platform/compat/path/index.js";
15
+ import { getProjectCandidates } from "../../../rendering/orchestrator/css-candidate-manifest.js";
16
+ import type { ResolvedContentContext } from "../../../platform/adapters/fs/veryfront/types.js";
15
17
  import type { HandlerContext } from "../types.js";
16
18
  import { FRAMEWORK_CANDIDATES } from "./framework-candidates.generated.js";
17
19
 
@@ -23,6 +25,25 @@ const SKIP_DIRS = new Set(["node_modules", ".cache", ".git", "dist", "build", ".
23
25
  /** De-duplicated set of framework candidates, computed once at import time. */
24
26
  const frameworkCandidates = new Set<string>(FRAMEWORK_CANDIDATES);
25
27
 
28
+ interface SourceFileProvider {
29
+ getAllSourceFiles?: () =>
30
+ | Array<{ path: string; content?: string }>
31
+ | Promise<Array<{ path: string; content?: string }>>;
32
+ getContentContext?: () => ResolvedContentContext | null;
33
+ }
34
+
35
+ function resolveProjectVersion(
36
+ ctx: HandlerContext,
37
+ contentContext: ResolvedContentContext | null,
38
+ ): string {
39
+ if (contentContext?.releaseId) return `release:${contentContext.releaseId}`;
40
+ if (contentContext?.branch) return `branch:${contentContext.branch}`;
41
+ if (contentContext?.environmentName) return `environment:${contentContext.environmentName}`;
42
+ if (ctx.releaseId) return `release:${ctx.releaseId}`;
43
+ if (ctx.parsedDomain?.branch) return `branch:${ctx.parsedDomain.branch}`;
44
+ return "live";
45
+ }
46
+
26
47
  /**
27
48
  * Extract Tailwind CSS candidate class names from all project source files.
28
49
  *
@@ -42,9 +63,8 @@ export async function extractProjectCandidates(ctx: HandlerContext): Promise<Set
42
63
 
43
64
  // Call method directly on wrappedFs to preserve 'this' context
44
65
  const fsAdapter = wrappedFs.getUnderlyingAdapter() as {
45
- getAllSourceFiles?: () =>
46
- | Array<{ path: string; content?: string }>
47
- | Promise<Array<{ path: string; content?: string }>>;
66
+ getAllSourceFiles?: SourceFileProvider["getAllSourceFiles"];
67
+ getContentContext?: SourceFileProvider["getContentContext"];
48
68
  };
49
69
 
50
70
  if (typeof fsAdapter.getAllSourceFiles !== "function") {
@@ -56,14 +76,20 @@ export async function extractProjectCandidates(ctx: HandlerContext): Promise<Set
56
76
 
57
77
  const candidates = new Set<string>(frameworkCandidates);
58
78
  const files = await fsAdapter.getAllSourceFiles();
59
-
60
- for (const file of files) {
61
- if (!file.content) continue;
62
- if (!SOURCE_EXTENSIONS.some((ext) => file.path.endsWith(ext))) continue;
63
-
64
- for (const cls of extractCandidates(file.content)) {
65
- candidates.add(cls);
66
- }
79
+ const contentContext = typeof fsAdapter.getContentContext === "function"
80
+ ? fsAdapter.getContentContext()
81
+ : null;
82
+
83
+ for (
84
+ const cls of getProjectCandidates({
85
+ projectScope: ctx.projectSlug ?? contentContext?.projectSlug ?? ctx.projectDir,
86
+ projectVersion: resolveProjectVersion(ctx, contentContext),
87
+ projectDir: ctx.projectDir,
88
+ files,
89
+ developmentMode: contentContext?.sourceType === "branch",
90
+ })
91
+ ) {
92
+ candidates.add(cls);
67
93
  }
68
94
 
69
95
  return candidates;
@@ -14,6 +14,7 @@ import { joinPath } from "../../../utils/path-utils.js";
14
14
  import {
15
15
  formatCSSError,
16
16
  generateTailwindCSS,
17
+ getProjectCSS,
17
18
  } from "../../../html/styles-builder/tailwind-compiler.js";
18
19
  import { DEFAULT_STYLESHEET } from "../../../html/styles-builder/css-hash-cache.js";
19
20
  import { serverLogger } from "../../../utils/index.js";
@@ -21,6 +22,10 @@ import { extractProjectCandidates } from "./styles-candidate-scanner.js";
21
22
 
22
23
  const logger = serverLogger.component("styles-css-handler");
23
24
 
25
+ type GeneratedStylesResult =
26
+ | Awaited<ReturnType<typeof generateTailwindCSS>>
27
+ | Awaited<ReturnType<typeof getProjectCSS>>;
28
+
24
29
  export class StylesCSSHandler extends BaseHandler {
25
30
  metadata: HandlerMetadata = {
26
31
  name: "StylesCSSHandler",
@@ -54,11 +59,9 @@ export class StylesCSSHandler extends BaseHandler {
54
59
  });
55
60
  candidates = new Set<string>();
56
61
  }
57
- const result = await generateTailwindCSS(rawCss, candidates, {
58
- projectSlug: ctx.projectSlug,
59
- });
62
+ const result = await this.generateStylesheet(ctx, rawCss, candidates);
60
63
 
61
- if (result.error) {
64
+ if ("error" in result && result.error) {
62
65
  const formatted = formatCSSError(result.error);
63
66
  logger.error("Tailwind error", {
64
67
  error: formatted.message,
@@ -104,6 +107,8 @@ body::before {
104
107
  logger.debug("CSS generated", {
105
108
  candidates: candidates.size,
106
109
  cssLength: result.css.length,
110
+ fromCache: "fromCache" in result ? result.fromCache : false,
111
+ cssHash: "hash" in result ? result.hash : undefined,
107
112
  });
108
113
 
109
114
  return this.respond(
@@ -144,4 +149,20 @@ body::before {
144
149
  return DEFAULT_STYLESHEET;
145
150
  }
146
151
  }
152
+
153
+ private generateStylesheet(
154
+ ctx: HandlerContext,
155
+ rawCss: string,
156
+ candidates: Set<string>,
157
+ ): Promise<GeneratedStylesResult> {
158
+ if (!ctx.projectSlug) {
159
+ return generateTailwindCSS(rawCss, candidates);
160
+ }
161
+
162
+ return getProjectCSS(ctx.projectSlug, rawCss, candidates, {
163
+ minify: true,
164
+ environment: "preview",
165
+ buildMode: "production",
166
+ });
167
+ }
147
168
  }
@@ -9,6 +9,8 @@ import { cwd } from "../../platform/compat/process.js";
9
9
  import { rendererLogger } from "../../utils/index.js";
10
10
  import { resolveImport } from "../../modules/import-map/resolver.js";
11
11
  import type { ImportMapConfig } from "../../modules/import-map/types.js";
12
+ import { buildEsmShUrl } from "../import-rewriter/url-builder.js";
13
+ import { parseBarePackageSpecifier } from "../shared/package-specifier.js";
12
14
  import { DEFAULT_REACT_VERSION, getReactImportMap } from "./package-registry.js";
13
15
 
14
16
  const logger = rendererLogger.component("http-cache");
@@ -149,7 +151,16 @@ export function resolveBareSpecifier(
149
151
  const mapped = resolveImport(specifier, importMap);
150
152
  if (mapped !== specifier) return mapped;
151
153
 
152
- return `https://esm.sh/${specifier}?target=es2022`;
154
+ const parsed = parseBarePackageSpecifier(specifier);
155
+ if (parsed == null) {
156
+ return `https://esm.sh/${specifier}?target=es2022`;
157
+ }
158
+
159
+ return buildEsmShUrl(
160
+ parsed.packageName,
161
+ parsed.version ?? undefined,
162
+ parsed.subpath ?? undefined,
163
+ );
153
164
  }
154
165
 
155
166
  /**
@@ -13,6 +13,7 @@ import type {
13
13
  RewriteResult,
14
14
  } from "../types.js";
15
15
  import { buildEsmShUrl, TAILWIND_VERSION } from "../url-builder.js";
16
+ import { parseBarePackageSpecifier } from "../../shared/package-specifier.js";
16
17
 
17
18
  const logger = rendererLogger.component("esm");
18
19
 
@@ -22,10 +23,6 @@ function hasVersionSpecifier(specifier: string): boolean {
22
23
  return /@[\d^~x][\d.x^~-]*(?=\/|$)/.test(specifier);
23
24
  }
24
25
 
25
- function normalizeVersionedSpecifier(specifier: string): string {
26
- return specifier.replace(/@[\d^~x][\d.x^~-]*(?=\/|$)/, "");
27
- }
28
-
29
26
  function warnUnversionedImport(specifier: string, projectId: string): void {
30
27
  const key = `${projectId}:${specifier}`;
31
28
  if (unversionedImportsWarned.has(key)) return;
@@ -73,20 +70,22 @@ export class BareStrategy implements ImportRewriteStrategy {
73
70
  rewrite(info: ImportSpecifierInfo, ctx: RewriteContext): RewriteResult {
74
71
  if (ctx.target === "ssr") return { specifier: null };
75
72
 
76
- const normalized = normalizeVersionedSpecifier(info.specifier);
73
+ const parsed = parseBarePackageSpecifier(info.specifier);
74
+ if (parsed == null) {
75
+ return { specifier: null };
76
+ }
77
77
 
78
- let finalSpecifier = normalized;
78
+ const packageName = parsed.packageName;
79
+ let version = parsed.version ?? undefined;
80
+ const subpath = parsed.subpath ?? undefined;
79
81
 
80
- if (normalized === "tailwindcss" || normalized.startsWith("tailwindcss/")) {
81
- finalSpecifier = normalized.replace(
82
- /^tailwindcss/,
83
- `tailwindcss@${TAILWIND_VERSION}`,
84
- );
82
+ if (packageName === "tailwindcss") {
83
+ version = TAILWIND_VERSION;
85
84
  } else if (!hasVersionSpecifier(info.specifier)) {
86
85
  warnUnversionedImport(info.specifier, ctx.projectId);
87
86
  }
88
87
 
89
- const url = buildEsmShUrl(finalSpecifier, undefined, undefined, {
88
+ const url = buildEsmShUrl(packageName, version, subpath, {
90
89
  external: ["react"],
91
90
  });
92
91
 
@@ -0,0 +1,29 @@
1
+ export interface ParsedBarePackageSpecifier {
2
+ packageName: string;
3
+ version: string | null;
4
+ subpath: string | null;
5
+ }
6
+
7
+ export function parseBarePackageSpecifier(
8
+ specifier: string,
9
+ ): ParsedBarePackageSpecifier | null {
10
+ const scopedMatch = specifier.match(/^(@[^/]+\/[^/@]+)(?:@([^/]+))?(\/.*)?$/);
11
+ if (scopedMatch?.[1]) {
12
+ return {
13
+ packageName: scopedMatch[1],
14
+ version: scopedMatch[2] ?? null,
15
+ subpath: scopedMatch[3] ?? null,
16
+ };
17
+ }
18
+
19
+ const unscopedMatch = specifier.match(/^([^/@][^/@]*?)(?:@([^/]+))?(\/.*)?$/);
20
+ if (unscopedMatch?.[1]) {
21
+ return {
22
+ packageName: unscopedMatch[1],
23
+ version: unscopedMatch[2] ?? null,
24
+ subpath: unscopedMatch[3] ?? null,
25
+ };
26
+ }
27
+
28
+ return null;
29
+ }