veryfront 0.1.89 → 0.1.91
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/esm/cli/commands/knowledge/command.d.ts +3 -0
- package/esm/cli/commands/knowledge/command.d.ts.map +1 -1
- package/esm/cli/commands/knowledge/command.js +27 -2
- package/esm/deno.js +1 -1
- package/esm/src/html/styles-builder/candidate-extractor.d.ts +5 -1
- package/esm/src/html/styles-builder/candidate-extractor.d.ts.map +1 -1
- package/esm/src/html/styles-builder/candidate-extractor.js +6 -1
- package/esm/src/html/styles-builder/content-version.d.ts +9 -0
- package/esm/src/html/styles-builder/content-version.d.ts.map +1 -0
- package/esm/src/html/styles-builder/content-version.js +15 -0
- package/esm/src/html/styles-builder/css-pregeneration.d.ts +11 -0
- package/esm/src/html/styles-builder/css-pregeneration.d.ts.map +1 -1
- package/esm/src/html/styles-builder/css-pregeneration.js +15 -10
- package/esm/src/html/styles-builder/prepared-project-css-cache.d.ts +31 -0
- package/esm/src/html/styles-builder/prepared-project-css-cache.d.ts.map +1 -0
- package/esm/src/html/styles-builder/prepared-project-css-cache.js +165 -0
- package/esm/src/html/styles-builder/style-scope-profile.d.ts +14 -0
- package/esm/src/html/styles-builder/style-scope-profile.d.ts.map +1 -0
- package/esm/src/html/styles-builder/style-scope-profile.js +121 -0
- package/esm/src/jobs/schemas.d.ts +30 -30
- package/esm/src/platform/adapters/fs/factory.d.ts.map +1 -1
- package/esm/src/platform/adapters/fs/factory.js +2 -21
- package/esm/src/platform/adapters/fs/veryfront/adapter.d.ts.map +1 -1
- package/esm/src/platform/adapters/fs/veryfront/adapter.js +44 -19
- package/esm/src/platform/adapters/fs/veryfront/default-invalidation-callbacks.d.ts +3 -0
- package/esm/src/platform/adapters/fs/veryfront/default-invalidation-callbacks.d.ts.map +1 -0
- package/esm/src/platform/adapters/fs/veryfront/default-invalidation-callbacks.js +25 -0
- package/esm/src/platform/adapters/fs/veryfront/proxy-manager.d.ts.map +1 -1
- package/esm/src/platform/adapters/fs/veryfront/proxy-manager.js +2 -15
- package/esm/src/platform/adapters/fs/veryfront/websocket-manager.d.ts +4 -0
- package/esm/src/platform/adapters/fs/veryfront/websocket-manager.d.ts.map +1 -1
- package/esm/src/platform/adapters/fs/veryfront/websocket-manager.js +2 -0
- package/esm/src/platform/adapters/veryfront-api-client/client.d.ts +3 -1
- package/esm/src/platform/adapters/veryfront-api-client/client.d.ts.map +1 -1
- package/esm/src/platform/adapters/veryfront-api-client/client.js +6 -0
- package/esm/src/platform/adapters/veryfront-api-client/index.d.ts +2 -2
- package/esm/src/platform/adapters/veryfront-api-client/index.d.ts.map +1 -1
- package/esm/src/platform/adapters/veryfront-api-client/index.js +1 -1
- package/esm/src/platform/adapters/veryfront-api-client/operations.d.ts +24 -0
- package/esm/src/platform/adapters/veryfront-api-client/operations.d.ts.map +1 -1
- package/esm/src/platform/adapters/veryfront-api-client/operations.js +65 -3
- package/esm/src/platform/adapters/veryfront-api-client/schemas/api.schema.d.ts +28 -0
- package/esm/src/platform/adapters/veryfront-api-client/schemas/api.schema.d.ts.map +1 -1
- package/esm/src/platform/adapters/veryfront-api-client/schemas/api.schema.js +13 -0
- package/esm/src/platform/adapters/veryfront-api-client/schemas/index.d.ts +1 -1
- package/esm/src/platform/adapters/veryfront-api-client/schemas/index.d.ts.map +1 -1
- package/esm/src/platform/adapters/veryfront-api-client/schemas/index.js +1 -1
- package/esm/src/rendering/orchestrator/css-candidate-manifest.d.ts +3 -0
- package/esm/src/rendering/orchestrator/css-candidate-manifest.d.ts.map +1 -1
- package/esm/src/rendering/orchestrator/css-candidate-manifest.js +10 -5
- package/esm/src/rendering/orchestrator/html.d.ts.map +1 -1
- package/esm/src/rendering/orchestrator/html.js +8 -0
- package/esm/src/sandbox/index.d.ts +1 -1
- package/esm/src/sandbox/index.d.ts.map +1 -1
- package/esm/src/sandbox/index.js +1 -1
- package/esm/src/sandbox/sandbox.d.ts +58 -0
- package/esm/src/sandbox/sandbox.d.ts.map +1 -1
- package/esm/src/sandbox/sandbox.js +111 -0
- package/esm/src/server/handlers/dev/styles-candidate-scanner.d.ts.map +1 -1
- package/esm/src/server/handlers/dev/styles-candidate-scanner.js +14 -16
- package/esm/src/server/handlers/dev/styles-css.handler.d.ts +7 -0
- package/esm/src/server/handlers/dev/styles-css.handler.d.ts.map +1 -1
- package/esm/src/server/handlers/dev/styles-css.handler.js +175 -8
- package/package.json +1 -1
- package/src/cli/commands/knowledge/command.ts +30 -2
- package/src/deno.js +1 -1
- package/src/src/html/styles-builder/candidate-extractor.ts +13 -0
- package/src/src/html/styles-builder/content-version.ts +20 -0
- package/src/src/html/styles-builder/css-pregeneration.ts +49 -12
- package/src/src/html/styles-builder/prepared-project-css-cache.ts +228 -0
- package/src/src/html/styles-builder/style-scope-profile.ts +164 -0
- package/src/src/platform/adapters/fs/factory.ts +2 -27
- package/src/src/platform/adapters/fs/veryfront/adapter.ts +49 -20
- package/src/src/platform/adapters/fs/veryfront/default-invalidation-callbacks.ts +35 -0
- package/src/src/platform/adapters/fs/veryfront/proxy-manager.ts +4 -21
- package/src/src/platform/adapters/fs/veryfront/websocket-manager.ts +3 -0
- package/src/src/platform/adapters/veryfront-api-client/client.ts +17 -0
- package/src/src/platform/adapters/veryfront-api-client/index.ts +6 -0
- package/src/src/platform/adapters/veryfront-api-client/operations.ts +110 -3
- package/src/src/platform/adapters/veryfront-api-client/schemas/api.schema.ts +16 -0
- package/src/src/platform/adapters/veryfront-api-client/schemas/index.ts +2 -0
- package/src/src/rendering/orchestrator/css-candidate-manifest.ts +28 -6
- package/src/src/rendering/orchestrator/html.ts +11 -0
- package/src/src/sandbox/index.ts +13 -1
- package/src/src/sandbox/sandbox.ts +183 -0
- package/src/src/server/handlers/dev/styles-candidate-scanner.ts +18 -15
- package/src/src/server/handlers/dev/styles-css.handler.ts +262 -12
|
@@ -9,6 +9,10 @@ export {
|
|
|
9
9
|
type FileDetail,
|
|
10
10
|
type FileListResult,
|
|
11
11
|
type ListFilesOptions,
|
|
12
|
+
type ProjectStyleArtifactResolution,
|
|
13
|
+
type ResolveStyleArtifactInput,
|
|
14
|
+
type StyleArtifactSelector,
|
|
15
|
+
type UpsertStyleArtifactInput,
|
|
12
16
|
VeryfrontAPIOperations,
|
|
13
17
|
} from "./operations.js";
|
|
14
18
|
export { type RequestOptions, requestWithRetry, type RetryConfig } from "./retry-handler.js";
|
|
@@ -39,4 +43,6 @@ export {
|
|
|
39
43
|
ProjectSchema,
|
|
40
44
|
ReleaseFileDetailSchema,
|
|
41
45
|
ReleaseFileListItemSchema,
|
|
46
|
+
type StyleArtifactResolveResponse,
|
|
47
|
+
StyleArtifactResolveResponseSchema,
|
|
42
48
|
} from "./schemas/index.js";
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { logger as baseLogger } from "../../../utils/index.js";
|
|
2
2
|
import { z } from "zod";
|
|
3
|
-
import { requestWithRetry, type RetryConfig } from "./retry-handler.js";
|
|
3
|
+
import { type RequestOptions, requestWithRetry, type RetryConfig } from "./retry-handler.js";
|
|
4
4
|
import { API_CLIENT_ERROR } from "./types.js";
|
|
5
5
|
import {
|
|
6
6
|
BranchFileDetailSchema,
|
|
@@ -15,6 +15,7 @@ import {
|
|
|
15
15
|
type ProjectFile,
|
|
16
16
|
ProjectSchema,
|
|
17
17
|
ReleaseFileDetailSchema,
|
|
18
|
+
StyleArtifactResolveResponseSchema,
|
|
18
19
|
} from "./schemas/index.js";
|
|
19
20
|
import { withSpan } from "../../../observability/tracing/otlp-setup.js";
|
|
20
21
|
import { SpanNames } from "../../../observability/tracing/span-names.js";
|
|
@@ -53,6 +54,32 @@ export interface FileDetail {
|
|
|
53
54
|
release_version?: string | null;
|
|
54
55
|
}
|
|
55
56
|
|
|
57
|
+
export interface StyleArtifactSelector {
|
|
58
|
+
branch?: string;
|
|
59
|
+
environmentName?: string;
|
|
60
|
+
releaseId?: string;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export interface ResolveStyleArtifactInput extends StyleArtifactSelector {
|
|
64
|
+
styleProfileHash: string;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export interface UpsertStyleArtifactInput extends ResolveStyleArtifactInput {
|
|
68
|
+
artifactHash: string;
|
|
69
|
+
assetPath?: string;
|
|
70
|
+
contentType?: string;
|
|
71
|
+
etag?: string;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export interface ProjectStyleArtifactResolution {
|
|
75
|
+
status: "ready" | "missing";
|
|
76
|
+
artifactHash?: string;
|
|
77
|
+
assetPath?: string;
|
|
78
|
+
etag?: string;
|
|
79
|
+
contentType?: string;
|
|
80
|
+
updatedAt?: string;
|
|
81
|
+
}
|
|
82
|
+
|
|
56
83
|
function buildListParams(options: ListFilesOptions): URLSearchParams {
|
|
57
84
|
const { cursor, limit = DEFAULT_PAGE_LIMIT, pattern, sortBy = "updated_at", sortOrder = "desc" } =
|
|
58
85
|
options;
|
|
@@ -81,6 +108,30 @@ function mapProjectFile<T extends ProjectFile>(file: T): ProjectFile {
|
|
|
81
108
|
};
|
|
82
109
|
}
|
|
83
110
|
|
|
111
|
+
function buildStyleArtifactParams(input: ResolveStyleArtifactInput): URLSearchParams {
|
|
112
|
+
const params = new URLSearchParams({
|
|
113
|
+
style_profile_hash: input.styleProfileHash,
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
if (input.branch) params.set("branch", input.branch);
|
|
117
|
+
if (input.environmentName) params.set("environment_name", input.environmentName);
|
|
118
|
+
if (input.releaseId) params.set("release_id", input.releaseId);
|
|
119
|
+
|
|
120
|
+
return params;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function mapStyleArtifactResolution(raw: unknown): ProjectStyleArtifactResolution {
|
|
124
|
+
const response = StyleArtifactResolveResponseSchema.parse(raw);
|
|
125
|
+
return {
|
|
126
|
+
status: response.status,
|
|
127
|
+
artifactHash: response.artifact_hash,
|
|
128
|
+
assetPath: response.asset_path,
|
|
129
|
+
etag: response.etag,
|
|
130
|
+
contentType: response.content_type,
|
|
131
|
+
updatedAt: response.updated_at,
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
|
|
84
135
|
async function listAllFiles(
|
|
85
136
|
list: (cursor?: string) => Promise<FileListResult>,
|
|
86
137
|
): Promise<ProjectFile[]> {
|
|
@@ -423,11 +474,67 @@ export class VeryfrontAPIOperations {
|
|
|
423
474
|
);
|
|
424
475
|
}
|
|
425
476
|
|
|
426
|
-
|
|
477
|
+
async resolveStyleArtifact(
|
|
478
|
+
projectRef: string,
|
|
479
|
+
input: ResolveStyleArtifactInput,
|
|
480
|
+
): Promise<ProjectStyleArtifactResolution> {
|
|
481
|
+
const params = buildStyleArtifactParams(input);
|
|
482
|
+
const url = `/projects/${encodeURIComponent(projectRef)}/style-artifacts/current?${params}`;
|
|
483
|
+
logger.debug("resolveStyleArtifact", {
|
|
484
|
+
projectRef,
|
|
485
|
+
branch: input.branch,
|
|
486
|
+
environmentName: input.environmentName,
|
|
487
|
+
releaseId: input.releaseId,
|
|
488
|
+
styleProfileHash: input.styleProfileHash,
|
|
489
|
+
});
|
|
490
|
+
|
|
491
|
+
return mapStyleArtifactResolution(await this.request(url));
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
async upsertStyleArtifact(
|
|
495
|
+
projectRef: string,
|
|
496
|
+
input: UpsertStyleArtifactInput,
|
|
497
|
+
): Promise<ProjectStyleArtifactResolution> {
|
|
498
|
+
const url = `/projects/${encodeURIComponent(projectRef)}/style-artifacts/current`;
|
|
499
|
+
logger.debug("upsertStyleArtifact", {
|
|
500
|
+
projectRef,
|
|
501
|
+
branch: input.branch,
|
|
502
|
+
environmentName: input.environmentName,
|
|
503
|
+
releaseId: input.releaseId,
|
|
504
|
+
styleProfileHash: input.styleProfileHash,
|
|
505
|
+
artifactHash: input.artifactHash,
|
|
506
|
+
});
|
|
507
|
+
|
|
508
|
+
return mapStyleArtifactResolution(
|
|
509
|
+
await this.request(url, {
|
|
510
|
+
method: "PUT",
|
|
511
|
+
headers: {
|
|
512
|
+
"Content-Type": "application/json",
|
|
513
|
+
},
|
|
514
|
+
body: JSON.stringify({
|
|
515
|
+
style_profile_hash: input.styleProfileHash,
|
|
516
|
+
branch: input.branch,
|
|
517
|
+
environment_name: input.environmentName,
|
|
518
|
+
release_id: input.releaseId,
|
|
519
|
+
artifact_hash: input.artifactHash,
|
|
520
|
+
asset_path: input.assetPath,
|
|
521
|
+
content_type: input.contentType,
|
|
522
|
+
etag: input.etag,
|
|
523
|
+
}),
|
|
524
|
+
}),
|
|
525
|
+
);
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
private request(endpoint: string, options: RequestOptions = {}): Promise<unknown> {
|
|
427
529
|
return withSpan(
|
|
428
530
|
SpanNames.API_REQUEST,
|
|
429
531
|
() =>
|
|
430
|
-
requestWithRetry(
|
|
532
|
+
requestWithRetry(
|
|
533
|
+
`${this.apiBaseUrl}${endpoint}`,
|
|
534
|
+
this.tokenProvider(),
|
|
535
|
+
this.retryConfig,
|
|
536
|
+
options,
|
|
537
|
+
),
|
|
431
538
|
{ "api.endpoint": endpoint, "api.base_url": this.apiBaseUrl },
|
|
432
539
|
);
|
|
433
540
|
}
|
|
@@ -147,6 +147,15 @@ export const LookupDomainResponseSchema = z.object({
|
|
|
147
147
|
release_id: z.string().uuid().nullable(),
|
|
148
148
|
});
|
|
149
149
|
|
|
150
|
+
export const StyleArtifactResolveResponseSchema = z.object({
|
|
151
|
+
status: z.enum(["ready", "missing"]),
|
|
152
|
+
artifact_hash: z.string().optional(),
|
|
153
|
+
asset_path: z.string().optional(),
|
|
154
|
+
etag: z.string().optional(),
|
|
155
|
+
content_type: z.string().optional(),
|
|
156
|
+
updated_at: z.string().optional(),
|
|
157
|
+
});
|
|
158
|
+
|
|
150
159
|
export type Project = z.infer<typeof ProjectSchema>;
|
|
151
160
|
export type ProjectFile = z.infer<typeof ProjectFileSchema>;
|
|
152
161
|
export type PageInfo = z.infer<typeof PageInfoSchema>;
|
|
@@ -165,6 +174,7 @@ export type ListReleaseFilesResponse = z.infer<typeof ListReleaseFilesResponseSc
|
|
|
165
174
|
export type ReleaseFileDetail = z.infer<typeof ReleaseFileDetailSchema>;
|
|
166
175
|
|
|
167
176
|
export type LookupDomainResponse = z.infer<typeof LookupDomainResponseSchema>;
|
|
177
|
+
export type StyleArtifactResolveResponse = z.infer<typeof StyleArtifactResolveResponseSchema>;
|
|
168
178
|
|
|
169
179
|
export const API_ENDPOINTS = {
|
|
170
180
|
listProjects: {
|
|
@@ -212,4 +222,10 @@ export const API_ENDPOINTS = {
|
|
|
212
222
|
path: "/projects/{domain}",
|
|
213
223
|
description: "Look up project by custom domain (resolved via project_reference)",
|
|
214
224
|
},
|
|
225
|
+
resolveStyleArtifact: {
|
|
226
|
+
method: "GET" as const,
|
|
227
|
+
path: "/projects/{projectRef}/style-artifacts/current",
|
|
228
|
+
description:
|
|
229
|
+
"Resolve metadata for the latest ready style artifact for a branch, environment, or release selector",
|
|
230
|
+
},
|
|
215
231
|
} as const;
|
|
@@ -1,4 +1,8 @@
|
|
|
1
1
|
import { extractCandidates } from "../../html/styles-builder/tailwind-compiler.js";
|
|
2
|
+
import {
|
|
3
|
+
filterFilesForStyleScope,
|
|
4
|
+
type StyleScopeProfile,
|
|
5
|
+
} from "../../html/styles-builder/style-scope-profile.js";
|
|
2
6
|
import { getRouteModulePaths } from "../../modules/manifest/route-module-manifest.js";
|
|
3
7
|
import { rendererLogger } from "../../utils/index.js";
|
|
4
8
|
|
|
@@ -17,6 +21,7 @@ interface RouteCandidateOptions {
|
|
|
17
21
|
projectScope: string;
|
|
18
22
|
projectVersion: string;
|
|
19
23
|
projectDir: string;
|
|
24
|
+
styleProfile?: StyleScopeProfile;
|
|
20
25
|
routeKey: string;
|
|
21
26
|
routeFilePaths: string[];
|
|
22
27
|
files: SourceFileLike[];
|
|
@@ -27,6 +32,7 @@ interface ProjectCandidateOptions {
|
|
|
27
32
|
projectScope: string;
|
|
28
33
|
projectVersion: string;
|
|
29
34
|
projectDir: string;
|
|
35
|
+
styleProfile?: StyleScopeProfile;
|
|
30
36
|
files: SourceFileLike[];
|
|
31
37
|
developmentMode: boolean;
|
|
32
38
|
}
|
|
@@ -51,8 +57,12 @@ function toRelativeProjectPath(path: string, projectDir: string): string {
|
|
|
51
57
|
return normalized.replace(/^\/+/, "");
|
|
52
58
|
}
|
|
53
59
|
|
|
54
|
-
function buildManifestCacheKey(
|
|
55
|
-
|
|
60
|
+
function buildManifestCacheKey(
|
|
61
|
+
projectScope: string,
|
|
62
|
+
projectVersion: string,
|
|
63
|
+
styleProfileHash?: string,
|
|
64
|
+
): string {
|
|
65
|
+
return `${projectScope}:${projectVersion}:${styleProfileHash ?? "default"}`;
|
|
56
66
|
}
|
|
57
67
|
|
|
58
68
|
function shouldRebuildManifest(
|
|
@@ -101,13 +111,20 @@ function buildCandidateManifest(files: SourceFileLike[], projectDir: string): Ca
|
|
|
101
111
|
function getOrBuildManifest(
|
|
102
112
|
options: Pick<
|
|
103
113
|
ProjectCandidateOptions,
|
|
104
|
-
"projectScope" | "projectVersion" | "projectDir" | "files" | "developmentMode"
|
|
114
|
+
"projectScope" | "projectVersion" | "projectDir" | "files" | "developmentMode" | "styleProfile"
|
|
105
115
|
>,
|
|
106
116
|
): CandidateManifest {
|
|
107
|
-
const manifestKey = buildManifestCacheKey(
|
|
117
|
+
const manifestKey = buildManifestCacheKey(
|
|
118
|
+
options.projectScope,
|
|
119
|
+
options.projectVersion,
|
|
120
|
+
options.styleProfile?.hash,
|
|
121
|
+
);
|
|
108
122
|
const existingManifest = manifestCache.get(manifestKey);
|
|
123
|
+
const scopedFiles = options.styleProfile
|
|
124
|
+
? filterFilesForStyleScope(options.files, options.styleProfile, options.projectDir)
|
|
125
|
+
: options.files;
|
|
109
126
|
const manifest = shouldRebuildManifest(existingManifest, options.developmentMode)
|
|
110
|
-
? buildCandidateManifest(
|
|
127
|
+
? buildCandidateManifest(scopedFiles, options.projectDir)
|
|
111
128
|
: existingManifest!;
|
|
112
129
|
|
|
113
130
|
if (manifest !== existingManifest) {
|
|
@@ -139,7 +156,11 @@ function addCandidatesForPath(
|
|
|
139
156
|
* Resolve route-scoped Tailwind candidates from a precomputed per-project manifest.
|
|
140
157
|
*/
|
|
141
158
|
export function getRouteCandidates(options: RouteCandidateOptions): Set<string> {
|
|
142
|
-
const manifestKey = buildManifestCacheKey(
|
|
159
|
+
const manifestKey = buildManifestCacheKey(
|
|
160
|
+
options.projectScope,
|
|
161
|
+
options.projectVersion,
|
|
162
|
+
options.styleProfile?.hash,
|
|
163
|
+
);
|
|
143
164
|
const manifest = getOrBuildManifest(options);
|
|
144
165
|
const routeCacheKey = `${manifestKey}:${options.routeKey}`;
|
|
145
166
|
const cachedRoute = routeCandidateCache.get(routeCacheKey);
|
|
@@ -167,6 +188,7 @@ export function getRouteCandidates(options: RouteCandidateOptions): Set<string>
|
|
|
167
188
|
logger.debug("Resolved route candidates", {
|
|
168
189
|
projectScope: options.projectScope,
|
|
169
190
|
projectVersion: options.projectVersion,
|
|
191
|
+
styleProfileHash: options.styleProfile?.hash,
|
|
170
192
|
route: options.routeKey,
|
|
171
193
|
count: routeCandidates.size,
|
|
172
194
|
});
|
|
@@ -29,6 +29,9 @@ import {
|
|
|
29
29
|
rewriteCssModuleContent,
|
|
30
30
|
} from "../../transforms/css-modules/naming.js";
|
|
31
31
|
import { getRouteCandidates } from "./css-candidate-manifest.js";
|
|
32
|
+
import { resolveStyleContentVersion } from "../../html/styles-builder/content-version.js";
|
|
33
|
+
import { createStyleScopeProfile } from "../../html/styles-builder/style-scope-profile.js";
|
|
34
|
+
import type { ResolvedContentContext } from "../../platform/adapters/fs/veryfront/types.js";
|
|
32
35
|
|
|
33
36
|
const logger = rendererLogger.component("html-generator");
|
|
34
37
|
|
|
@@ -453,9 +456,16 @@ export class HTMLGenerator {
|
|
|
453
456
|
if (typeof wrappedFs.getUnderlyingAdapter !== "function") return undefined;
|
|
454
457
|
|
|
455
458
|
const fsAdapter = wrappedFs.getUnderlyingAdapter() as {
|
|
459
|
+
getContentContext?: () => ResolvedContentContext | null;
|
|
456
460
|
getProjectData?: () => { updated_at?: string } | undefined;
|
|
457
461
|
};
|
|
458
462
|
|
|
463
|
+
const contentContext = typeof fsAdapter.getContentContext === "function"
|
|
464
|
+
? fsAdapter.getContentContext()
|
|
465
|
+
: null;
|
|
466
|
+
|
|
467
|
+
if (contentContext) return resolveStyleContentVersion(contentContext);
|
|
468
|
+
|
|
459
469
|
return fsAdapter.getProjectData?.()?.updated_at;
|
|
460
470
|
}
|
|
461
471
|
|
|
@@ -505,6 +515,7 @@ export class HTMLGenerator {
|
|
|
505
515
|
projectScope,
|
|
506
516
|
projectVersion,
|
|
507
517
|
projectDir: this.config.projectDir,
|
|
518
|
+
styleProfile: createStyleScopeProfile(this.config.config),
|
|
508
519
|
routeKey,
|
|
509
520
|
routeFilePaths,
|
|
510
521
|
files,
|
package/src/src/sandbox/index.ts
CHANGED
|
@@ -19,4 +19,16 @@
|
|
|
19
19
|
import "../../_dnt.polyfills.js";
|
|
20
20
|
|
|
21
21
|
|
|
22
|
-
export {
|
|
22
|
+
export {
|
|
23
|
+
type CommandJob,
|
|
24
|
+
type CommandJobHeartbeatStatus,
|
|
25
|
+
type CommandJobOutput,
|
|
26
|
+
type CommandJobStatus,
|
|
27
|
+
type ExecResult,
|
|
28
|
+
type ExecStreamEvent,
|
|
29
|
+
Sandbox,
|
|
30
|
+
type SandboxListOptions,
|
|
31
|
+
type SandboxListResult,
|
|
32
|
+
type SandboxOptions,
|
|
33
|
+
type SandboxSession,
|
|
34
|
+
} from "./sandbox.js";
|
|
@@ -41,6 +41,60 @@ export interface ExecStreamEvent {
|
|
|
41
41
|
exitCode?: number;
|
|
42
42
|
}
|
|
43
43
|
|
|
44
|
+
/** Status of an async command job. */
|
|
45
|
+
export type CommandJobStatus = "running" | "completed" | "failed" | "canceled";
|
|
46
|
+
|
|
47
|
+
/** Heartbeat health status for a command job. */
|
|
48
|
+
export type CommandJobHeartbeatStatus = "disabled" | "healthy" | "degraded";
|
|
49
|
+
|
|
50
|
+
/** An async command job running in a sandbox. */
|
|
51
|
+
export interface CommandJob {
|
|
52
|
+
id: string;
|
|
53
|
+
status: CommandJobStatus;
|
|
54
|
+
exitCode: number | null;
|
|
55
|
+
signal: string | null;
|
|
56
|
+
startedAt: string;
|
|
57
|
+
finishedAt: string | null;
|
|
58
|
+
heartbeatStatus: CommandJobHeartbeatStatus;
|
|
59
|
+
lastHeartbeatAt: string | null;
|
|
60
|
+
lastHeartbeatError: string | null;
|
|
61
|
+
heartbeatFailureCount: number;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/** A command job with its captured output. */
|
|
65
|
+
export interface CommandJobOutput extends CommandJob {
|
|
66
|
+
stdout: string;
|
|
67
|
+
stderr: string;
|
|
68
|
+
stdoutTruncated: boolean;
|
|
69
|
+
stderrTruncated: boolean;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/** A sandbox session summary returned by list. */
|
|
73
|
+
export interface SandboxSession {
|
|
74
|
+
id: string;
|
|
75
|
+
shortId: string;
|
|
76
|
+
endpoint: string;
|
|
77
|
+
status: string;
|
|
78
|
+
createdAt: string;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/** Options for listing sandbox sessions. */
|
|
82
|
+
export interface SandboxListOptions extends SandboxOptions {
|
|
83
|
+
cursor?: string;
|
|
84
|
+
limit?: number;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/** Paginated result of sandbox sessions. */
|
|
88
|
+
export interface SandboxListResult {
|
|
89
|
+
data: SandboxSession[];
|
|
90
|
+
pageInfo: {
|
|
91
|
+
self: string | null;
|
|
92
|
+
first: null;
|
|
93
|
+
next: string | null;
|
|
94
|
+
prev: string | null;
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
|
|
44
98
|
/** Client for isolated ephemeral compute environments with command execution and file I/O. */
|
|
45
99
|
export class Sandbox {
|
|
46
100
|
private constructor(
|
|
@@ -117,6 +171,47 @@ export class Sandbox {
|
|
|
117
171
|
return new Sandbox(endpoint, id, authToken, apiUrl);
|
|
118
172
|
}
|
|
119
173
|
|
|
174
|
+
/** List sandbox sessions with optional pagination. */
|
|
175
|
+
static async list(options: SandboxListOptions = {}): Promise<SandboxListResult> {
|
|
176
|
+
const apiUrl = Sandbox.resolveApiUrl(options);
|
|
177
|
+
const authToken = Sandbox.resolveAuthToken(options);
|
|
178
|
+
|
|
179
|
+
const params = new URLSearchParams();
|
|
180
|
+
if (options.cursor) params.set("cursor", options.cursor);
|
|
181
|
+
if (options.limit !== undefined) params.set("limit", String(options.limit));
|
|
182
|
+
|
|
183
|
+
const query = params.toString();
|
|
184
|
+
const url = `${apiUrl}/sandbox-sessions${query ? `?${query}` : ""}`;
|
|
185
|
+
|
|
186
|
+
const res = await dntShim.fetch(url, {
|
|
187
|
+
headers: { Authorization: `Bearer ${authToken}` },
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
if (!res.ok) {
|
|
191
|
+
throw REQUEST_ERROR.create({
|
|
192
|
+
detail: `Failed to list sandboxes: ${res.status} ${await res.text()}`,
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
const json = await res.json();
|
|
197
|
+
|
|
198
|
+
return {
|
|
199
|
+
data: json.data.map((s: Record<string, unknown>) => ({
|
|
200
|
+
id: s.id,
|
|
201
|
+
shortId: s.short_id,
|
|
202
|
+
endpoint: s.endpoint,
|
|
203
|
+
status: s.status,
|
|
204
|
+
createdAt: s.created_at,
|
|
205
|
+
})),
|
|
206
|
+
pageInfo: {
|
|
207
|
+
self: json.page_info?.self ?? null,
|
|
208
|
+
first: null,
|
|
209
|
+
next: json.page_info?.next ?? null,
|
|
210
|
+
prev: json.page_info?.prev ?? null,
|
|
211
|
+
},
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
|
|
120
215
|
private static async waitForReady(
|
|
121
216
|
apiUrl: string,
|
|
122
217
|
id: string,
|
|
@@ -247,6 +342,94 @@ export class Sandbox {
|
|
|
247
342
|
}
|
|
248
343
|
}
|
|
249
344
|
|
|
345
|
+
/** Start an async command job in the sandbox. */
|
|
346
|
+
async startCommandJob(command: string): Promise<CommandJob> {
|
|
347
|
+
const res = await dntShim.fetch(`${this.endpoint}/exec/jobs`, {
|
|
348
|
+
method: "POST",
|
|
349
|
+
headers: {
|
|
350
|
+
Authorization: `Bearer ${this.authToken}`,
|
|
351
|
+
"Content-Type": "application/json",
|
|
352
|
+
},
|
|
353
|
+
body: JSON.stringify({ command }),
|
|
354
|
+
});
|
|
355
|
+
|
|
356
|
+
if (!res.ok) {
|
|
357
|
+
throw REQUEST_ERROR.create({
|
|
358
|
+
detail: `Start command job failed: ${res.status} ${await res.text()}`,
|
|
359
|
+
});
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
return Sandbox.mapCommandJob(await res.json());
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
/** Get the status of an async command job. */
|
|
366
|
+
async getCommandJob(jobId: string): Promise<CommandJob> {
|
|
367
|
+
const res = await dntShim.fetch(`${this.endpoint}/exec/jobs/${jobId}`, {
|
|
368
|
+
headers: { Authorization: `Bearer ${this.authToken}` },
|
|
369
|
+
});
|
|
370
|
+
|
|
371
|
+
if (!res.ok) {
|
|
372
|
+
throw REQUEST_ERROR.create({
|
|
373
|
+
detail: `Get command job failed: ${res.status} ${await res.text()}`,
|
|
374
|
+
});
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
return Sandbox.mapCommandJob(await res.json());
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
/** Get the output of an async command job. */
|
|
381
|
+
async getCommandJobOutput(jobId: string): Promise<CommandJobOutput> {
|
|
382
|
+
const res = await dntShim.fetch(`${this.endpoint}/exec/jobs/${jobId}/output`, {
|
|
383
|
+
headers: { Authorization: `Bearer ${this.authToken}` },
|
|
384
|
+
});
|
|
385
|
+
|
|
386
|
+
if (!res.ok) {
|
|
387
|
+
throw REQUEST_ERROR.create({
|
|
388
|
+
detail: `Get command job output failed: ${res.status} ${await res.text()}`,
|
|
389
|
+
});
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
const json = await res.json();
|
|
393
|
+
return {
|
|
394
|
+
...Sandbox.mapCommandJob(json),
|
|
395
|
+
stdout: json.stdout,
|
|
396
|
+
stderr: json.stderr,
|
|
397
|
+
stdoutTruncated: json.stdout_truncated,
|
|
398
|
+
stderrTruncated: json.stderr_truncated,
|
|
399
|
+
};
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
/** Cancel an async command job. */
|
|
403
|
+
async cancelCommandJob(jobId: string): Promise<CommandJob> {
|
|
404
|
+
const res = await dntShim.fetch(`${this.endpoint}/exec/jobs/${jobId}/cancel`, {
|
|
405
|
+
method: "POST",
|
|
406
|
+
headers: { Authorization: `Bearer ${this.authToken}` },
|
|
407
|
+
});
|
|
408
|
+
|
|
409
|
+
if (!res.ok) {
|
|
410
|
+
throw REQUEST_ERROR.create({
|
|
411
|
+
detail: `Cancel command job failed: ${res.status} ${await res.text()}`,
|
|
412
|
+
});
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
return Sandbox.mapCommandJob(await res.json());
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
private static mapCommandJob(json: Record<string, unknown>): CommandJob {
|
|
419
|
+
return {
|
|
420
|
+
id: json.id as string,
|
|
421
|
+
status: json.status as CommandJobStatus,
|
|
422
|
+
exitCode: json.exit_code as number | null,
|
|
423
|
+
signal: json.signal as string | null,
|
|
424
|
+
startedAt: json.started_at as string,
|
|
425
|
+
finishedAt: json.finished_at as string | null,
|
|
426
|
+
heartbeatStatus: json.heartbeat_status as CommandJobHeartbeatStatus,
|
|
427
|
+
lastHeartbeatAt: json.last_heartbeat_at as string | null,
|
|
428
|
+
lastHeartbeatError: json.last_heartbeat_error as string | null,
|
|
429
|
+
heartbeatFailureCount: json.heartbeat_failure_count as number,
|
|
430
|
+
};
|
|
431
|
+
}
|
|
432
|
+
|
|
250
433
|
/** Send a heartbeat to prevent idle timeout. */
|
|
251
434
|
async heartbeat(): Promise<void> {
|
|
252
435
|
await dntShim.fetch(`${this.apiUrl}/sandbox-sessions/${this.sessionId}/heartbeat`, {
|
|
@@ -9,6 +9,12 @@
|
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
11
|
import { extractCandidates } from "../../../html/styles-builder/tailwind-compiler.js";
|
|
12
|
+
import { resolveStyleContentVersion } from "../../../html/styles-builder/content-version.js";
|
|
13
|
+
import {
|
|
14
|
+
createStyleScopeProfile,
|
|
15
|
+
shouldIncludeStylePath,
|
|
16
|
+
shouldTraverseStyleDirectory,
|
|
17
|
+
} from "../../../html/styles-builder/style-scope-profile.js";
|
|
12
18
|
import { serverLogger } from "../../../utils/index.js";
|
|
13
19
|
import { createFileSystem } from "../../../platform/compat/fs.js";
|
|
14
20
|
import { join } from "../../../platform/compat/path/index.js";
|
|
@@ -20,7 +26,6 @@ import { FRAMEWORK_CANDIDATES } from "./framework-candidates.generated.js";
|
|
|
20
26
|
const logger = serverLogger.component("styles-candidate-scanner");
|
|
21
27
|
|
|
22
28
|
const SOURCE_EXTENSIONS = [".tsx", ".jsx", ".mdx", ".ts", ".js"];
|
|
23
|
-
const SKIP_DIRS = new Set(["node_modules", ".cache", ".git", "dist", "build", ".vscode"]);
|
|
24
29
|
|
|
25
30
|
/** De-duplicated set of framework candidates, computed once at import time. */
|
|
26
31
|
const frameworkCandidates = new Set<string>(FRAMEWORK_CANDIDATES);
|
|
@@ -32,18 +37,6 @@ interface SourceFileProvider {
|
|
|
32
37
|
getContentContext?: () => ResolvedContentContext | null;
|
|
33
38
|
}
|
|
34
39
|
|
|
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
|
-
|
|
47
40
|
/**
|
|
48
41
|
* Extract Tailwind CSS candidate class names from all project source files.
|
|
49
42
|
*
|
|
@@ -52,6 +45,7 @@ function resolveProjectVersion(
|
|
|
52
45
|
* method is available (local dev mode).
|
|
53
46
|
*/
|
|
54
47
|
export async function extractProjectCandidates(ctx: HandlerContext): Promise<Set<string>> {
|
|
48
|
+
const styleProfile = createStyleScopeProfile(ctx.config);
|
|
55
49
|
const wrappedFs = ctx.adapter.fs as { getUnderlyingAdapter?: () => unknown };
|
|
56
50
|
|
|
57
51
|
if (typeof wrappedFs.getUnderlyingAdapter !== "function") {
|
|
@@ -83,8 +77,13 @@ export async function extractProjectCandidates(ctx: HandlerContext): Promise<Set
|
|
|
83
77
|
for (
|
|
84
78
|
const cls of getProjectCandidates({
|
|
85
79
|
projectScope: ctx.projectSlug ?? contentContext?.projectSlug ?? ctx.projectDir,
|
|
86
|
-
projectVersion:
|
|
80
|
+
projectVersion: resolveStyleContentVersion(contentContext, {
|
|
81
|
+
releaseId: ctx.releaseId,
|
|
82
|
+
branch: ctx.parsedDomain?.branch,
|
|
83
|
+
environmentName: ctx.environmentName,
|
|
84
|
+
}),
|
|
87
85
|
projectDir: ctx.projectDir,
|
|
86
|
+
styleProfile,
|
|
88
87
|
files,
|
|
89
88
|
developmentMode: contentContext?.sourceType === "branch",
|
|
90
89
|
})
|
|
@@ -100,6 +99,7 @@ export async function extractProjectCandidates(ctx: HandlerContext): Promise<Set
|
|
|
100
99
|
* Used in local development mode where projects are read directly from disk.
|
|
101
100
|
*/
|
|
102
101
|
async function scanLocalFiles(projectDir: string, ctx: HandlerContext): Promise<Set<string>> {
|
|
102
|
+
const styleProfile = createStyleScopeProfile(ctx.config);
|
|
103
103
|
const candidates = new Set<string>(frameworkCandidates);
|
|
104
104
|
const fs = createFileSystem();
|
|
105
105
|
|
|
@@ -116,11 +116,14 @@ async function scanLocalFiles(projectDir: string, ctx: HandlerContext): Promise<
|
|
|
116
116
|
const fullPath = join(dir, entry.name);
|
|
117
117
|
|
|
118
118
|
if (entry.isDirectory) {
|
|
119
|
-
if (
|
|
119
|
+
if (shouldTraverseStyleDirectory(styleProfile, fullPath, projectDir)) {
|
|
120
|
+
await scanDir(fullPath);
|
|
121
|
+
}
|
|
120
122
|
continue;
|
|
121
123
|
}
|
|
122
124
|
|
|
123
125
|
if (!entry.isFile) continue;
|
|
126
|
+
if (!shouldIncludeStylePath(styleProfile, fullPath, projectDir)) continue;
|
|
124
127
|
if (!SOURCE_EXTENSIONS.some((ext) => entry.name.endsWith(ext))) continue;
|
|
125
128
|
|
|
126
129
|
try {
|