@caryhu/codemine-forge 0.0.1-alpha.0 → 0.0.1-alpha.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +8 -4
- package/dist/build-pipeline/browser-bundle.d.ts +2 -1
- package/dist/build-pipeline/browser-bundle.js +384 -24
- package/dist/build-pipeline/diagnostics.d.ts +1 -0
- package/dist/build-pipeline/diagnostics.js +11 -2
- package/dist/build-pipeline/index.d.ts +2 -2
- package/dist/build-pipeline/index.js +2 -1
- package/dist/build-pipeline/pipeline.d.ts +2 -0
- package/dist/build-pipeline/pipeline.js +149 -28
- package/dist/build-pipeline/types.d.ts +24 -2
- package/dist/build-pipeline/vite-config.d.ts +8 -0
- package/dist/build-pipeline/vite-config.js +215 -0
- package/dist/dev-server-preview/index.d.ts +2 -2
- package/dist/dev-server-preview/index.js +2 -1
- package/dist/dev-server-preview/preview-document.d.ts +2 -1
- package/dist/dev-server-preview/preview-document.js +203 -1
- package/dist/dev-server-preview/server.d.ts +8 -1
- package/dist/dev-server-preview/server.js +248 -33
- package/dist/dev-server-preview/types.d.ts +40 -1
- package/dist/forge-controller.js +1 -0
- package/dist/index.d.ts +5 -4
- package/dist/index.js +16 -2
- package/dist/package-resolution/diagnostics.d.ts +1 -0
- package/dist/package-resolution/diagnostics.js +10 -1
- package/dist/package-resolution/errors.d.ts +2 -1
- package/dist/package-resolution/errors.js +2 -1
- package/dist/package-resolution/index.d.ts +2 -2
- package/dist/package-resolution/index.js +2 -1
- package/dist/package-resolution/resolver.d.ts +3 -0
- package/dist/package-resolution/resolver.js +123 -29
- package/dist/package-resolution/types.d.ts +7 -1
- package/dist/telemetry/index.d.ts +68 -0
- package/dist/telemetry/index.js +65 -0
- package/dist/terminal-commands/npm-build.js +3 -1
- package/dist/terminal-commands/npm-dev.js +3 -1
- package/dist/terminal-commands/npm-install.js +7 -2
- package/dist/terminal-commands/terminal.d.ts +1 -0
- package/dist/terminal-commands/terminal.js +1 -0
- package/dist/terminal-commands/types.d.ts +2 -0
- package/dist/virtual-file-system/diagnostics.d.ts +1 -0
- package/dist/virtual-file-system/diagnostics.js +9 -1
- package/dist/virtual-file-system/index.d.ts +4 -2
- package/dist/virtual-file-system/index.js +7 -1
- package/dist/virtual-file-system/snapshot-serialization.d.ts +3 -0
- package/dist/virtual-file-system/snapshot-serialization.js +242 -0
- package/dist/virtual-file-system/storage.d.ts +1 -1
- package/dist/virtual-file-system/storage.js +41 -18
- package/dist/virtual-file-system/types.d.ts +28 -1
- package/dist/virtual-file-system/types.js +2 -0
- package/package.json +2 -2
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.emitForgeTelemetry = exports.createForgeUnsupportedPackageReason = exports.summarizeForgeTelemetryDiagnostics = exports.completeForgeTelemetryTiming = exports.startForgeTelemetryTiming = exports.TELEMETRY_CAPABILITY = void 0;
|
|
4
|
+
exports.TELEMETRY_CAPABILITY = "telemetry";
|
|
5
|
+
function startForgeTelemetryTiming(startedAt) {
|
|
6
|
+
return {
|
|
7
|
+
startedAt,
|
|
8
|
+
startedMs: Date.now(),
|
|
9
|
+
};
|
|
10
|
+
}
|
|
11
|
+
exports.startForgeTelemetryTiming = startForgeTelemetryTiming;
|
|
12
|
+
function completeForgeTelemetryTiming(start, endedAt) {
|
|
13
|
+
return {
|
|
14
|
+
startedAt: start.startedAt,
|
|
15
|
+
endedAt,
|
|
16
|
+
durationMs: Math.max(0, Date.now() - start.startedMs),
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
exports.completeForgeTelemetryTiming = completeForgeTelemetryTiming;
|
|
20
|
+
function summarizeForgeTelemetryDiagnostics(diagnostics) {
|
|
21
|
+
return diagnostics.map((diagnostic) => ({
|
|
22
|
+
code: diagnostic.code,
|
|
23
|
+
severity: diagnostic.severity,
|
|
24
|
+
message: diagnostic.message,
|
|
25
|
+
path: diagnostic.path,
|
|
26
|
+
dependencyName: diagnostic.dependencyName,
|
|
27
|
+
requestedRange: diagnostic.requestedRange,
|
|
28
|
+
importer: diagnostic.importer,
|
|
29
|
+
importSpecifier: diagnostic.importSpecifier,
|
|
30
|
+
limitName: diagnostic.limit?.name,
|
|
31
|
+
}));
|
|
32
|
+
}
|
|
33
|
+
exports.summarizeForgeTelemetryDiagnostics = summarizeForgeTelemetryDiagnostics;
|
|
34
|
+
function createForgeUnsupportedPackageReason(diagnostic) {
|
|
35
|
+
if (!isUnsupportedPackageDiagnosticCode(diagnostic.code)) {
|
|
36
|
+
return undefined;
|
|
37
|
+
}
|
|
38
|
+
return {
|
|
39
|
+
code: diagnostic.code,
|
|
40
|
+
message: diagnostic.message,
|
|
41
|
+
dependencyName: diagnostic.dependencyName,
|
|
42
|
+
requestedRange: diagnostic.requestedRange,
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
exports.createForgeUnsupportedPackageReason = createForgeUnsupportedPackageReason;
|
|
46
|
+
function emitForgeTelemetry(reporter, event) {
|
|
47
|
+
if (reporter === undefined) {
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
try {
|
|
51
|
+
reporter(event);
|
|
52
|
+
}
|
|
53
|
+
catch {
|
|
54
|
+
// Telemetry is observational and must not alter runtime behavior.
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
exports.emitForgeTelemetry = emitForgeTelemetry;
|
|
58
|
+
function isUnsupportedPackageDiagnosticCode(code) {
|
|
59
|
+
return [
|
|
60
|
+
"invalid_dependency",
|
|
61
|
+
"offline_cache_miss",
|
|
62
|
+
"unresolved_package",
|
|
63
|
+
"unsupported_range",
|
|
64
|
+
].includes(code);
|
|
65
|
+
}
|
|
@@ -20,7 +20,9 @@ class NpmRunBuildCommandHandler {
|
|
|
20
20
|
diagnostics: [],
|
|
21
21
|
};
|
|
22
22
|
}
|
|
23
|
-
const buildResult = await context.buildPipeline.build(
|
|
23
|
+
const buildResult = await context.buildPipeline.build({
|
|
24
|
+
signal: context.signal,
|
|
25
|
+
});
|
|
24
26
|
if (!buildResult.success) {
|
|
25
27
|
const firstDiagnostic = buildResult.diagnostics[0];
|
|
26
28
|
return {
|
|
@@ -20,7 +20,9 @@ class NpmRunDevCommandHandler {
|
|
|
20
20
|
diagnostics: [],
|
|
21
21
|
};
|
|
22
22
|
}
|
|
23
|
-
const startResult = await context.devServerPreview.start(
|
|
23
|
+
const startResult = await context.devServerPreview.start({
|
|
24
|
+
signal: context.signal,
|
|
25
|
+
});
|
|
24
26
|
if (!startResult.success) {
|
|
25
27
|
const firstDiagnostic = startResult.diagnostics[0];
|
|
26
28
|
return {
|
|
@@ -25,8 +25,8 @@ class NpmInstallCommandHandler {
|
|
|
25
25
|
return {
|
|
26
26
|
command: context.command,
|
|
27
27
|
exitCode: 0,
|
|
28
|
-
output,
|
|
29
|
-
diagnostics:
|
|
28
|
+
output: [...output, ...warningOutput(packageResolution.warnings)],
|
|
29
|
+
diagnostics: packageResolution.warnings,
|
|
30
30
|
packageResolution,
|
|
31
31
|
};
|
|
32
32
|
}
|
|
@@ -54,3 +54,8 @@ function stdout(text) {
|
|
|
54
54
|
function stderr(text) {
|
|
55
55
|
return { kind: "stderr", text };
|
|
56
56
|
}
|
|
57
|
+
function warningOutput(diagnostics) {
|
|
58
|
+
return diagnostics
|
|
59
|
+
.filter((diagnostic) => diagnostic.severity === "warning")
|
|
60
|
+
.map((diagnostic) => stderr(`Forge warning: ${diagnostic.message}`));
|
|
61
|
+
}
|
|
@@ -7,6 +7,7 @@ export declare class DefaultForgeTerminal implements ForgeTerminal {
|
|
|
7
7
|
constructor(options: ForgeTerminalOptions);
|
|
8
8
|
execute(commandText: string, executeOptions?: {
|
|
9
9
|
readonly cwd?: string;
|
|
10
|
+
readonly signal?: AbortSignal;
|
|
10
11
|
}): Promise<ForgeCommandResult>;
|
|
11
12
|
}
|
|
12
13
|
export declare function createForgeTerminal(options: ForgeTerminalOptions): ForgeTerminal;
|
|
@@ -35,6 +35,7 @@ export interface ForgeCommandContext {
|
|
|
35
35
|
readonly buildPipeline?: ForgeBuildPipeline;
|
|
36
36
|
readonly devServerPreview?: ForgeDevServerPreview;
|
|
37
37
|
readonly packageResolver: ForgePackageResolver;
|
|
38
|
+
readonly signal?: AbortSignal;
|
|
38
39
|
}
|
|
39
40
|
export interface ForgeCommandHandler {
|
|
40
41
|
readonly name: string;
|
|
@@ -44,6 +45,7 @@ export interface ForgeCommandHandler {
|
|
|
44
45
|
}
|
|
45
46
|
export interface ForgeTerminalExecuteOptions {
|
|
46
47
|
readonly cwd?: string;
|
|
48
|
+
readonly signal?: AbortSignal;
|
|
47
49
|
}
|
|
48
50
|
export interface ForgeTerminalOptions {
|
|
49
51
|
readonly buildPipeline?: ForgeBuildPipeline;
|
|
@@ -8,6 +8,7 @@ export interface ForgeDiagnosticInput {
|
|
|
8
8
|
}
|
|
9
9
|
export declare function createForgeDiagnostic(input: ForgeDiagnosticInput): ForgeDiagnostic;
|
|
10
10
|
export declare function createInvalidPathDiagnostic(path: string, message?: string): ForgeDiagnostic;
|
|
11
|
+
export declare function createInvalidSnapshotDiagnostic(message?: string, cause?: unknown): ForgeDiagnostic;
|
|
11
12
|
export declare function createPathTraversalDiagnostic(path: string): ForgeDiagnostic;
|
|
12
13
|
export declare function createNotFoundDiagnostic(path: string): ForgeDiagnostic;
|
|
13
14
|
export declare function createMissingParentDiagnostic(path: string): ForgeDiagnostic;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.createNotImplementedDiagnostic = exports.createMissingParentDiagnostic = exports.createNotFoundDiagnostic = exports.createPathTraversalDiagnostic = exports.createInvalidPathDiagnostic = exports.createForgeDiagnostic = void 0;
|
|
3
|
+
exports.createNotImplementedDiagnostic = exports.createMissingParentDiagnostic = exports.createNotFoundDiagnostic = exports.createPathTraversalDiagnostic = exports.createInvalidSnapshotDiagnostic = exports.createInvalidPathDiagnostic = exports.createForgeDiagnostic = void 0;
|
|
4
4
|
function createForgeDiagnostic(input) {
|
|
5
5
|
return {
|
|
6
6
|
code: input.code,
|
|
@@ -20,6 +20,14 @@ function createInvalidPathDiagnostic(path, message = "Path is invalid.") {
|
|
|
20
20
|
});
|
|
21
21
|
}
|
|
22
22
|
exports.createInvalidPathDiagnostic = createInvalidPathDiagnostic;
|
|
23
|
+
function createInvalidSnapshotDiagnostic(message = "Workspace snapshot payload is invalid.", cause) {
|
|
24
|
+
return createForgeDiagnostic({
|
|
25
|
+
code: "invalid_snapshot",
|
|
26
|
+
message,
|
|
27
|
+
cause,
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
exports.createInvalidSnapshotDiagnostic = createInvalidSnapshotDiagnostic;
|
|
23
31
|
function createPathTraversalDiagnostic(path) {
|
|
24
32
|
return createForgeDiagnostic({
|
|
25
33
|
code: "path_traversal",
|
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
import type { ForgeDeleteOptions, ForgeDirEntry, ForgeFileStat, ForgeFileWatchListener, ForgeMkdirOptions, ForgeOpenVirtualFileSystemOptions, ForgeRestoreOptions, ForgeSnapshot, ForgeSnapshotOptions, ForgeUnsubscribe, ForgeVirtualFileSystemOptions, ForgeWriteFileOptions } from "./types";
|
|
2
2
|
export { DEFAULT_FORGE_VFS_LIMITS, createLimitExceededDiagnostic, resolveForgeVirtualFileSystemLimits, } from "./limits";
|
|
3
|
-
export { createForgeDiagnostic, createInvalidPathDiagnostic, createMissingParentDiagnostic, createNotFoundDiagnostic, createNotImplementedDiagnostic, createPathTraversalDiagnostic, type ForgeDiagnosticInput, } from "./diagnostics";
|
|
3
|
+
export { createForgeDiagnostic, createInvalidSnapshotDiagnostic, createInvalidPathDiagnostic, createMissingParentDiagnostic, createNotFoundDiagnostic, createNotImplementedDiagnostic, createPathTraversalDiagnostic, type ForgeDiagnosticInput, } from "./diagnostics";
|
|
4
4
|
export { normalizeForgePath, type ForgePathNormalizationResult, } from "./path";
|
|
5
5
|
export { ForgeFileSystemError } from "./errors";
|
|
6
6
|
export { InMemoryVirtualFileSystem } from "./memory";
|
|
7
7
|
export { createIndexedDbForgeStorage, FORGE_INDEXED_DB_NAME, FORGE_INDEXED_DB_STORES, FORGE_INDEXED_DB_VERSION, IndexedDbForgeStorage, type IndexedDbForgeStorageOptions, } from "./storage";
|
|
8
|
-
export
|
|
8
|
+
export { exportWorkspaceSnapshot, importWorkspaceSnapshot, } from "./snapshot-serialization";
|
|
9
|
+
export { FORGE_WORKSPACE_SNAPSHOT_EXPORT_SCHEMA_VERSION } from "./types";
|
|
10
|
+
export type { ForgeBuildCacheRecord, ForgeDeleteOptions, ForgeDiagnostic, ForgeDiagnosticCode, ForgeDiagnosticSeverity, ForgeDirEntry, ForgeFileCreatedEvent, ForgeFileDeletedEvent, ForgeFileEvent, ForgeFileKind, ForgeFileRecord, ForgeFileRenamedEvent, ForgeFileSource, ForgeFileStat, ForgeFileUpdatedEvent, ForgeFileWatchListener, ForgeLimitDiagnostic, ForgeMkdirOptions, ForgeOpenVirtualFileSystemOptions, ForgePackageCacheRecord, ForgePersistedWorkspace, ForgeSerializedFileRecord, ForgeSerializedWorkspaceSnapshot, ForgeSerializedWorkspaceSnapshotPayload, ForgeVirtualFileSystemLimits, ResolvedForgeVirtualFileSystemLimits, ForgeRestoreOptions, ForgeSnapshot, ForgeSnapshotOptions, ForgeUnsubscribe, ForgeVirtualFileSystemOptions, ForgeVirtualFileSystemStorage, ForgeWorkspace, ForgeWorkspaceMetadata, ForgeWorkspaceSnapshotExportOptions, ForgeWriteFileOptions, } from "./types";
|
|
9
11
|
export declare const VIRTUAL_FILE_SYSTEM_CAPABILITY: "virtual-file-system";
|
|
10
12
|
export interface ForgeVirtualFileSystem {
|
|
11
13
|
readonly capability: typeof VIRTUAL_FILE_SYSTEM_CAPABILITY;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.openVirtualFileSystem = exports.createVirtualFileSystem = exports.VIRTUAL_FILE_SYSTEM_CAPABILITY = exports.IndexedDbForgeStorage = exports.FORGE_INDEXED_DB_VERSION = exports.FORGE_INDEXED_DB_STORES = exports.FORGE_INDEXED_DB_NAME = exports.createIndexedDbForgeStorage = exports.InMemoryVirtualFileSystem = exports.ForgeFileSystemError = exports.normalizeForgePath = exports.createPathTraversalDiagnostic = exports.createNotImplementedDiagnostic = exports.createNotFoundDiagnostic = exports.createMissingParentDiagnostic = exports.createInvalidPathDiagnostic = exports.createForgeDiagnostic = exports.resolveForgeVirtualFileSystemLimits = exports.createLimitExceededDiagnostic = exports.DEFAULT_FORGE_VFS_LIMITS = void 0;
|
|
3
|
+
exports.openVirtualFileSystem = exports.createVirtualFileSystem = exports.VIRTUAL_FILE_SYSTEM_CAPABILITY = exports.FORGE_WORKSPACE_SNAPSHOT_EXPORT_SCHEMA_VERSION = exports.importWorkspaceSnapshot = exports.exportWorkspaceSnapshot = exports.IndexedDbForgeStorage = exports.FORGE_INDEXED_DB_VERSION = exports.FORGE_INDEXED_DB_STORES = exports.FORGE_INDEXED_DB_NAME = exports.createIndexedDbForgeStorage = exports.InMemoryVirtualFileSystem = exports.ForgeFileSystemError = exports.normalizeForgePath = exports.createPathTraversalDiagnostic = exports.createNotImplementedDiagnostic = exports.createNotFoundDiagnostic = exports.createMissingParentDiagnostic = exports.createInvalidPathDiagnostic = exports.createInvalidSnapshotDiagnostic = exports.createForgeDiagnostic = exports.resolveForgeVirtualFileSystemLimits = exports.createLimitExceededDiagnostic = exports.DEFAULT_FORGE_VFS_LIMITS = void 0;
|
|
4
4
|
const memory_1 = require("./memory");
|
|
5
5
|
const storage_1 = require("./storage");
|
|
6
6
|
var limits_1 = require("./limits");
|
|
@@ -9,6 +9,7 @@ Object.defineProperty(exports, "createLimitExceededDiagnostic", { enumerable: tr
|
|
|
9
9
|
Object.defineProperty(exports, "resolveForgeVirtualFileSystemLimits", { enumerable: true, get: function () { return limits_1.resolveForgeVirtualFileSystemLimits; } });
|
|
10
10
|
var diagnostics_1 = require("./diagnostics");
|
|
11
11
|
Object.defineProperty(exports, "createForgeDiagnostic", { enumerable: true, get: function () { return diagnostics_1.createForgeDiagnostic; } });
|
|
12
|
+
Object.defineProperty(exports, "createInvalidSnapshotDiagnostic", { enumerable: true, get: function () { return diagnostics_1.createInvalidSnapshotDiagnostic; } });
|
|
12
13
|
Object.defineProperty(exports, "createInvalidPathDiagnostic", { enumerable: true, get: function () { return diagnostics_1.createInvalidPathDiagnostic; } });
|
|
13
14
|
Object.defineProperty(exports, "createMissingParentDiagnostic", { enumerable: true, get: function () { return diagnostics_1.createMissingParentDiagnostic; } });
|
|
14
15
|
Object.defineProperty(exports, "createNotFoundDiagnostic", { enumerable: true, get: function () { return diagnostics_1.createNotFoundDiagnostic; } });
|
|
@@ -26,6 +27,11 @@ Object.defineProperty(exports, "FORGE_INDEXED_DB_NAME", { enumerable: true, get:
|
|
|
26
27
|
Object.defineProperty(exports, "FORGE_INDEXED_DB_STORES", { enumerable: true, get: function () { return storage_2.FORGE_INDEXED_DB_STORES; } });
|
|
27
28
|
Object.defineProperty(exports, "FORGE_INDEXED_DB_VERSION", { enumerable: true, get: function () { return storage_2.FORGE_INDEXED_DB_VERSION; } });
|
|
28
29
|
Object.defineProperty(exports, "IndexedDbForgeStorage", { enumerable: true, get: function () { return storage_2.IndexedDbForgeStorage; } });
|
|
30
|
+
var snapshot_serialization_1 = require("./snapshot-serialization");
|
|
31
|
+
Object.defineProperty(exports, "exportWorkspaceSnapshot", { enumerable: true, get: function () { return snapshot_serialization_1.exportWorkspaceSnapshot; } });
|
|
32
|
+
Object.defineProperty(exports, "importWorkspaceSnapshot", { enumerable: true, get: function () { return snapshot_serialization_1.importWorkspaceSnapshot; } });
|
|
33
|
+
var types_1 = require("./types");
|
|
34
|
+
Object.defineProperty(exports, "FORGE_WORKSPACE_SNAPSHOT_EXPORT_SCHEMA_VERSION", { enumerable: true, get: function () { return types_1.FORGE_WORKSPACE_SNAPSHOT_EXPORT_SCHEMA_VERSION; } });
|
|
29
35
|
exports.VIRTUAL_FILE_SYSTEM_CAPABILITY = "virtual-file-system";
|
|
30
36
|
function createVirtualFileSystem(options) {
|
|
31
37
|
return new memory_1.InMemoryVirtualFileSystem(options);
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import type { ForgeSerializedWorkspaceSnapshotPayload, ForgeSnapshot, ForgeWorkspaceSnapshotExportOptions } from "./types";
|
|
2
|
+
export declare function exportWorkspaceSnapshot(snapshot: ForgeSnapshot, options?: ForgeWorkspaceSnapshotExportOptions): string;
|
|
3
|
+
export declare function importWorkspaceSnapshot(payload: string | ForgeSerializedWorkspaceSnapshotPayload): ForgeSnapshot;
|
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.importWorkspaceSnapshot = exports.exportWorkspaceSnapshot = void 0;
|
|
4
|
+
const diagnostics_1 = require("./diagnostics");
|
|
5
|
+
const errors_1 = require("./errors");
|
|
6
|
+
const types_1 = require("./types");
|
|
7
|
+
const BASE64_ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
|
8
|
+
const BASE64_LOOKUP = createBase64Lookup();
|
|
9
|
+
const VALID_FILE_KINDS = new Set(["file", "directory"]);
|
|
10
|
+
const VALID_SNAPSHOT_SOURCES = new Set([
|
|
11
|
+
"generated",
|
|
12
|
+
"template",
|
|
13
|
+
"user",
|
|
14
|
+
]);
|
|
15
|
+
function exportWorkspaceSnapshot(snapshot, options = {}) {
|
|
16
|
+
const payload = {
|
|
17
|
+
schemaVersion: types_1.FORGE_WORKSPACE_SNAPSHOT_EXPORT_SCHEMA_VERSION,
|
|
18
|
+
snapshot: {
|
|
19
|
+
id: snapshot.id,
|
|
20
|
+
workspaceId: snapshot.workspaceId,
|
|
21
|
+
createdAt: snapshot.createdAt,
|
|
22
|
+
files: snapshot.files
|
|
23
|
+
.filter((file) => file.source !== "cache")
|
|
24
|
+
.map(serializeFileRecord)
|
|
25
|
+
.sort(compareSerializedFileRecords),
|
|
26
|
+
lock: snapshot.lock,
|
|
27
|
+
metadata: snapshot.metadata,
|
|
28
|
+
},
|
|
29
|
+
};
|
|
30
|
+
return stableStringify(payload, options.space);
|
|
31
|
+
}
|
|
32
|
+
exports.exportWorkspaceSnapshot = exportWorkspaceSnapshot;
|
|
33
|
+
function importWorkspaceSnapshot(payload) {
|
|
34
|
+
const parsedPayload = typeof payload === "string" ? parseSnapshotPayload(payload) : payload;
|
|
35
|
+
assertPlainObject(parsedPayload, "Snapshot payload must be an object.");
|
|
36
|
+
assertEqual(parsedPayload.schemaVersion, types_1.FORGE_WORKSPACE_SNAPSHOT_EXPORT_SCHEMA_VERSION, "Snapshot payload schema version is unsupported.");
|
|
37
|
+
const snapshot = parsedPayload.snapshot;
|
|
38
|
+
assertPlainObject(snapshot, "Snapshot payload is missing snapshot data.");
|
|
39
|
+
assertString(snapshot.id, "Snapshot id must be a string.");
|
|
40
|
+
assertString(snapshot.workspaceId, "Snapshot workspace id must be a string.");
|
|
41
|
+
assertString(snapshot.createdAt, "Snapshot createdAt must be a string.");
|
|
42
|
+
assertArray(snapshot.files, "Snapshot files must be an array.");
|
|
43
|
+
return {
|
|
44
|
+
id: snapshot.id,
|
|
45
|
+
workspaceId: snapshot.workspaceId,
|
|
46
|
+
createdAt: snapshot.createdAt,
|
|
47
|
+
files: snapshot.files.map(importFileRecord),
|
|
48
|
+
lock: snapshot.lock,
|
|
49
|
+
metadata: snapshot.metadata === undefined
|
|
50
|
+
? undefined
|
|
51
|
+
: importMetadata(snapshot.metadata),
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
exports.importWorkspaceSnapshot = importWorkspaceSnapshot;
|
|
55
|
+
function parseSnapshotPayload(payload) {
|
|
56
|
+
try {
|
|
57
|
+
return JSON.parse(payload);
|
|
58
|
+
}
|
|
59
|
+
catch (cause) {
|
|
60
|
+
throw invalidSnapshot("Snapshot payload must be valid JSON.", cause);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
function serializeFileRecord(file) {
|
|
64
|
+
return {
|
|
65
|
+
path: file.path,
|
|
66
|
+
kind: file.kind,
|
|
67
|
+
contentBase64: file.content === undefined ? undefined : encodeBase64(file.content),
|
|
68
|
+
contentType: file.contentType,
|
|
69
|
+
contentHash: file.contentHash,
|
|
70
|
+
size: file.size,
|
|
71
|
+
createdAt: file.createdAt,
|
|
72
|
+
updatedAt: file.updatedAt,
|
|
73
|
+
source: file.source,
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
function importFileRecord(file) {
|
|
77
|
+
assertPlainObject(file, "Snapshot file record must be an object.");
|
|
78
|
+
assertString(file.path, "Snapshot file path must be a string.");
|
|
79
|
+
assertString(file.kind, "Snapshot file kind must be a string.");
|
|
80
|
+
assertString(file.createdAt, "Snapshot file createdAt must be a string.");
|
|
81
|
+
assertString(file.updatedAt, "Snapshot file updatedAt must be a string.");
|
|
82
|
+
assertString(file.source, "Snapshot file source must be a string.");
|
|
83
|
+
assertNumber(file.size, "Snapshot file size must be a finite number.");
|
|
84
|
+
if (!VALID_FILE_KINDS.has(file.kind)) {
|
|
85
|
+
throw invalidSnapshot("Snapshot file kind is unsupported.");
|
|
86
|
+
}
|
|
87
|
+
if (!VALID_SNAPSHOT_SOURCES.has(file.source)) {
|
|
88
|
+
throw invalidSnapshot("Snapshot file source is unsupported.");
|
|
89
|
+
}
|
|
90
|
+
assertOptionalString(file.contentBase64, "Snapshot file content must be base64 text.");
|
|
91
|
+
assertOptionalString(file.contentType, "Snapshot file content type must be a string.");
|
|
92
|
+
assertOptionalString(file.contentHash, "Snapshot file content hash must be a string.");
|
|
93
|
+
return {
|
|
94
|
+
path: file.path,
|
|
95
|
+
kind: file.kind,
|
|
96
|
+
content: file.contentBase64 === undefined
|
|
97
|
+
? undefined
|
|
98
|
+
: decodeBase64(file.contentBase64),
|
|
99
|
+
contentType: file.contentType,
|
|
100
|
+
contentHash: file.contentHash,
|
|
101
|
+
size: file.size,
|
|
102
|
+
createdAt: file.createdAt,
|
|
103
|
+
updatedAt: file.updatedAt,
|
|
104
|
+
source: file.source,
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
function importMetadata(metadata) {
|
|
108
|
+
assertPlainObject(metadata, "Snapshot metadata must be an object.");
|
|
109
|
+
return metadata;
|
|
110
|
+
}
|
|
111
|
+
function compareSerializedFileRecords(left, right) {
|
|
112
|
+
const pathOrder = left.path.localeCompare(right.path);
|
|
113
|
+
return pathOrder === 0 ? left.kind.localeCompare(right.kind) : pathOrder;
|
|
114
|
+
}
|
|
115
|
+
function stableStringify(value, space) {
|
|
116
|
+
return JSON.stringify(toStableJsonValue(value), undefined, space);
|
|
117
|
+
}
|
|
118
|
+
function toStableJsonValue(value, seen = new Set()) {
|
|
119
|
+
if (value === null || typeof value !== "object") {
|
|
120
|
+
return value;
|
|
121
|
+
}
|
|
122
|
+
const withToJson = value;
|
|
123
|
+
if (typeof withToJson.toJSON === "function") {
|
|
124
|
+
return toStableJsonValue(withToJson.toJSON(), seen);
|
|
125
|
+
}
|
|
126
|
+
if (seen.has(value)) {
|
|
127
|
+
throw new TypeError("Cannot serialize circular snapshot metadata.");
|
|
128
|
+
}
|
|
129
|
+
seen.add(value);
|
|
130
|
+
if (Array.isArray(value)) {
|
|
131
|
+
const result = value.map((item) => toStableJsonValue(item, seen));
|
|
132
|
+
seen.delete(value);
|
|
133
|
+
return result;
|
|
134
|
+
}
|
|
135
|
+
const record = value;
|
|
136
|
+
const result = {};
|
|
137
|
+
for (const key of Object.keys(record).sort()) {
|
|
138
|
+
const stableValue = toStableJsonValue(record[key], seen);
|
|
139
|
+
if (stableValue !== undefined) {
|
|
140
|
+
result[key] = stableValue;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
seen.delete(value);
|
|
144
|
+
return result;
|
|
145
|
+
}
|
|
146
|
+
function encodeBase64(bytes) {
|
|
147
|
+
let output = "";
|
|
148
|
+
for (let index = 0; index < bytes.length; index += 3) {
|
|
149
|
+
const first = bytes[index];
|
|
150
|
+
const second = bytes[index + 1];
|
|
151
|
+
const third = bytes[index + 2];
|
|
152
|
+
const hasSecond = index + 1 < bytes.length;
|
|
153
|
+
const hasThird = index + 2 < bytes.length;
|
|
154
|
+
const value = (first << 16) | ((hasSecond ? second : 0) << 8) | (hasThird ? third : 0);
|
|
155
|
+
output += BASE64_ALPHABET[(value >> 18) & 0x3f];
|
|
156
|
+
output += BASE64_ALPHABET[(value >> 12) & 0x3f];
|
|
157
|
+
output += hasSecond ? BASE64_ALPHABET[(value >> 6) & 0x3f] : "=";
|
|
158
|
+
output += hasThird ? BASE64_ALPHABET[value & 0x3f] : "=";
|
|
159
|
+
}
|
|
160
|
+
return output;
|
|
161
|
+
}
|
|
162
|
+
function decodeBase64(value) {
|
|
163
|
+
if (value.length === 0) {
|
|
164
|
+
return new Uint8Array();
|
|
165
|
+
}
|
|
166
|
+
if (value.length % 4 !== 0 ||
|
|
167
|
+
!/^[A-Za-z0-9+/]+={0,2}$/.test(value) ||
|
|
168
|
+
/=[A-Za-z0-9+/]/.test(value)) {
|
|
169
|
+
throw invalidSnapshot("Snapshot file content is not valid base64.");
|
|
170
|
+
}
|
|
171
|
+
const padding = value.endsWith("==") ? 2 : value.endsWith("=") ? 1 : 0;
|
|
172
|
+
const bytes = new Uint8Array((value.length / 4) * 3 - padding);
|
|
173
|
+
let byteIndex = 0;
|
|
174
|
+
for (let index = 0; index < value.length; index += 4) {
|
|
175
|
+
const first = base64Value(value[index]);
|
|
176
|
+
const second = base64Value(value[index + 1]);
|
|
177
|
+
const third = value[index + 2] === "=" ? 0 : base64Value(value[index + 2]);
|
|
178
|
+
const fourth = value[index + 3] === "=" ? 0 : base64Value(value[index + 3]);
|
|
179
|
+
const packed = (first << 18) | (second << 12) | (third << 6) | fourth;
|
|
180
|
+
if (byteIndex < bytes.length) {
|
|
181
|
+
bytes[byteIndex] = (packed >> 16) & 0xff;
|
|
182
|
+
byteIndex += 1;
|
|
183
|
+
}
|
|
184
|
+
if (byteIndex < bytes.length) {
|
|
185
|
+
bytes[byteIndex] = (packed >> 8) & 0xff;
|
|
186
|
+
byteIndex += 1;
|
|
187
|
+
}
|
|
188
|
+
if (byteIndex < bytes.length) {
|
|
189
|
+
bytes[byteIndex] = packed & 0xff;
|
|
190
|
+
byteIndex += 1;
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
if (encodeBase64(bytes) !== value) {
|
|
194
|
+
throw invalidSnapshot("Snapshot file content is not canonical base64.");
|
|
195
|
+
}
|
|
196
|
+
return bytes;
|
|
197
|
+
}
|
|
198
|
+
function base64Value(character) {
|
|
199
|
+
const value = BASE64_LOOKUP.get(character);
|
|
200
|
+
if (value === undefined) {
|
|
201
|
+
throw invalidSnapshot("Snapshot file content is not valid base64.");
|
|
202
|
+
}
|
|
203
|
+
return value;
|
|
204
|
+
}
|
|
205
|
+
function createBase64Lookup() {
|
|
206
|
+
return new Map(Array.from(BASE64_ALPHABET, (character, index) => [character, index]));
|
|
207
|
+
}
|
|
208
|
+
function assertPlainObject(value, message) {
|
|
209
|
+
if (value === null ||
|
|
210
|
+
typeof value !== "object" ||
|
|
211
|
+
Array.isArray(value)) {
|
|
212
|
+
throw invalidSnapshot(message);
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
function assertString(value, message) {
|
|
216
|
+
if (typeof value !== "string") {
|
|
217
|
+
throw invalidSnapshot(message);
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
function assertOptionalString(value, message) {
|
|
221
|
+
if (value !== undefined && typeof value !== "string") {
|
|
222
|
+
throw invalidSnapshot(message);
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
function assertNumber(value, message) {
|
|
226
|
+
if (typeof value !== "number" || !Number.isFinite(value) || value < 0) {
|
|
227
|
+
throw invalidSnapshot(message);
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
function assertArray(value, message) {
|
|
231
|
+
if (!Array.isArray(value)) {
|
|
232
|
+
throw invalidSnapshot(message);
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
function assertEqual(actual, expected, message) {
|
|
236
|
+
if (actual !== expected) {
|
|
237
|
+
throw invalidSnapshot(message);
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
function invalidSnapshot(message, cause) {
|
|
241
|
+
return new errors_1.ForgeFileSystemError((0, diagnostics_1.createInvalidSnapshotDiagnostic)(message, cause));
|
|
242
|
+
}
|
|
@@ -26,7 +26,7 @@ export declare class IndexedDbForgeStorage implements ForgeVirtualFileSystemStor
|
|
|
26
26
|
clearBuildCache(): Promise<void>;
|
|
27
27
|
private getCacheRecord;
|
|
28
28
|
private putCacheRecord;
|
|
29
|
-
private
|
|
29
|
+
private cacheLimitMaximum;
|
|
30
30
|
private openDatabase;
|
|
31
31
|
}
|
|
32
32
|
export declare function createIndexedDbForgeStorage(options?: IndexedDbForgeStorageOptions): IndexedDbForgeStorage;
|
|
@@ -100,30 +100,21 @@ class IndexedDbForgeStorage {
|
|
|
100
100
|
return record;
|
|
101
101
|
}
|
|
102
102
|
async putCacheRecord(storeName, record) {
|
|
103
|
-
await this.assertCacheLimit(storeName, record);
|
|
104
103
|
const database = await this.openDatabase();
|
|
105
104
|
const transaction = database.transaction([storeName], "readwrite");
|
|
106
|
-
transaction.objectStore(storeName)
|
|
105
|
+
const objectStore = transaction.objectStore(storeName);
|
|
106
|
+
const existingRecords = await requestToPromise(objectStore.getAll());
|
|
107
|
+
const evictionKeys = planCacheEviction(storeName, record, existingRecords, this.cacheLimitMaximum(storeName));
|
|
108
|
+
for (const key of evictionKeys) {
|
|
109
|
+
objectStore.delete(key);
|
|
110
|
+
}
|
|
111
|
+
objectStore.put(record);
|
|
107
112
|
await transactionDone(transaction);
|
|
108
113
|
}
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
? "packageCacheSize"
|
|
112
|
-
: "buildCacheSize";
|
|
113
|
-
const maximum = storeName === exports.FORGE_INDEXED_DB_STORES.packageCache
|
|
114
|
+
cacheLimitMaximum(storeName) {
|
|
115
|
+
return storeName === exports.FORGE_INDEXED_DB_STORES.packageCache
|
|
114
116
|
? this.limits.maxPackageCacheSizeBytes
|
|
115
117
|
: this.limits.maxBuildCacheSizeBytes;
|
|
116
|
-
const database = await this.openDatabase();
|
|
117
|
-
const transaction = database.transaction([storeName], "readonly");
|
|
118
|
-
const existingRecords = await requestToPromise(transaction.objectStore(storeName).getAll());
|
|
119
|
-
await transactionDone(transaction);
|
|
120
|
-
const existingSize = existingRecords
|
|
121
|
-
.filter((existingRecord) => existingRecord.key !== record.key)
|
|
122
|
-
.reduce((total, existingRecord) => total + existingRecord.size, 0);
|
|
123
|
-
const actual = existingSize + record.size;
|
|
124
|
-
if (actual > maximum) {
|
|
125
|
-
throw new errors_1.ForgeFileSystemError((0, limits_1.createLimitExceededDiagnostic)(limitName, actual, maximum));
|
|
126
|
-
}
|
|
127
118
|
}
|
|
128
119
|
openDatabase() {
|
|
129
120
|
if (this.databasePromise !== undefined) {
|
|
@@ -149,6 +140,38 @@ function createIndexedDbForgeStorage(options) {
|
|
|
149
140
|
return new IndexedDbForgeStorage(options);
|
|
150
141
|
}
|
|
151
142
|
exports.createIndexedDbForgeStorage = createIndexedDbForgeStorage;
|
|
143
|
+
function planCacheEviction(storeName, record, existingRecords, maximum) {
|
|
144
|
+
const limitName = storeName === exports.FORGE_INDEXED_DB_STORES.packageCache
|
|
145
|
+
? "packageCacheSize"
|
|
146
|
+
: "buildCacheSize";
|
|
147
|
+
if (record.size > maximum) {
|
|
148
|
+
throw new errors_1.ForgeFileSystemError((0, limits_1.createLimitExceededDiagnostic)(limitName, record.size, maximum));
|
|
149
|
+
}
|
|
150
|
+
const candidates = existingRecords
|
|
151
|
+
.filter((existingRecord) => existingRecord.key !== record.key)
|
|
152
|
+
.sort(compareCacheRecordsForEviction);
|
|
153
|
+
let projectedSize = candidates.reduce((total, existingRecord) => total + existingRecord.size, 0) +
|
|
154
|
+
record.size;
|
|
155
|
+
if (projectedSize <= maximum) {
|
|
156
|
+
return [];
|
|
157
|
+
}
|
|
158
|
+
const evictionKeys = [];
|
|
159
|
+
for (const candidate of candidates) {
|
|
160
|
+
evictionKeys.push(candidate.key);
|
|
161
|
+
projectedSize -= candidate.size;
|
|
162
|
+
if (projectedSize <= maximum) {
|
|
163
|
+
return evictionKeys;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
throw new errors_1.ForgeFileSystemError((0, limits_1.createLimitExceededDiagnostic)(limitName, projectedSize, maximum));
|
|
167
|
+
}
|
|
168
|
+
function compareCacheRecordsForEviction(left, right) {
|
|
169
|
+
const updatedAtComparison = left.updatedAt.localeCompare(right.updatedAt);
|
|
170
|
+
if (updatedAtComparison !== 0) {
|
|
171
|
+
return updatedAtComparison;
|
|
172
|
+
}
|
|
173
|
+
return left.key.localeCompare(right.key);
|
|
174
|
+
}
|
|
152
175
|
function ensureStores(database) {
|
|
153
176
|
if (!database.objectStoreNames.contains(exports.FORGE_INDEXED_DB_STORES.workspaceMetadata)) {
|
|
154
177
|
database.createObjectStore(exports.FORGE_INDEXED_DB_STORES.workspaceMetadata, {
|
|
@@ -47,8 +47,35 @@ export interface ForgeSnapshot {
|
|
|
47
47
|
readonly lock?: unknown;
|
|
48
48
|
readonly metadata?: Record<string, unknown>;
|
|
49
49
|
}
|
|
50
|
+
export declare const FORGE_WORKSPACE_SNAPSHOT_EXPORT_SCHEMA_VERSION = 1;
|
|
51
|
+
export interface ForgeWorkspaceSnapshotExportOptions {
|
|
52
|
+
readonly space?: number;
|
|
53
|
+
}
|
|
54
|
+
export interface ForgeSerializedWorkspaceSnapshotPayload {
|
|
55
|
+
readonly schemaVersion: typeof FORGE_WORKSPACE_SNAPSHOT_EXPORT_SCHEMA_VERSION;
|
|
56
|
+
readonly snapshot: ForgeSerializedWorkspaceSnapshot;
|
|
57
|
+
}
|
|
58
|
+
export interface ForgeSerializedWorkspaceSnapshot {
|
|
59
|
+
readonly id: string;
|
|
60
|
+
readonly workspaceId: string;
|
|
61
|
+
readonly createdAt: string;
|
|
62
|
+
readonly files: readonly ForgeSerializedFileRecord[];
|
|
63
|
+
readonly lock?: unknown;
|
|
64
|
+
readonly metadata?: Record<string, unknown>;
|
|
65
|
+
}
|
|
66
|
+
export interface ForgeSerializedFileRecord {
|
|
67
|
+
readonly path: string;
|
|
68
|
+
readonly kind: ForgeFileKind;
|
|
69
|
+
readonly contentBase64?: string;
|
|
70
|
+
readonly contentType?: string;
|
|
71
|
+
readonly contentHash?: string;
|
|
72
|
+
readonly size: number;
|
|
73
|
+
readonly createdAt: string;
|
|
74
|
+
readonly updatedAt: string;
|
|
75
|
+
readonly source: Exclude<ForgeFileSource, "cache">;
|
|
76
|
+
}
|
|
50
77
|
export type ForgeDiagnosticSeverity = "info" | "warning" | "error";
|
|
51
|
-
export type ForgeDiagnosticCode = "already_exists" | "directory_not_empty" | "invalid_path" | "is_directory" | "limit_exceeded" | "missing_parent" | "not_directory" | "not_file" | "not_found" | "not_implemented" | "path_traversal" | "storage_unavailable";
|
|
78
|
+
export type ForgeDiagnosticCode = "already_exists" | "directory_not_empty" | "invalid_snapshot" | "invalid_path" | "is_directory" | "limit_exceeded" | "missing_parent" | "not_directory" | "not_file" | "not_found" | "not_implemented" | "path_traversal" | "storage_unavailable";
|
|
52
79
|
export interface ForgeDiagnostic {
|
|
53
80
|
readonly code: ForgeDiagnosticCode;
|
|
54
81
|
readonly severity: ForgeDiagnosticSeverity;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@caryhu/codemine-forge",
|
|
3
|
-
"version": "0.0.1-alpha.
|
|
3
|
+
"version": "0.0.1-alpha.2",
|
|
4
4
|
"description": "Browser-side runtime layer for Codemine demos.",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"types": "./dist/index.d.ts",
|
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
"publish:npm": "node scripts/publish-npm.mjs",
|
|
35
35
|
"typecheck": "tsc -p tsconfig.json",
|
|
36
36
|
"test:build": "tsc -p tsconfig.test.json",
|
|
37
|
-
"test": "pnpm run test:build && node test/virtual-file-system.test.mjs && node test/indexeddb-workspace-restore.test.mjs && node test/react-vite-template-dev-loop.test.mjs && node test/react-vite-template-diagnostics.test.mjs && node test/package-resolution.test.mjs && node test/build-pipeline.test.mjs && node test/dev-server-preview.test.mjs && node test/terminal-commands.test.mjs && node test/esbuild-wasm-worker.test.mjs && node test/browser-preview-execution.test.mjs && node test/demo-samples.test.mjs",
|
|
37
|
+
"test": "pnpm run test:build && node test/virtual-file-system.test.mjs && node test/indexeddb-workspace-restore.test.mjs && node test/react-vite-template-dev-loop.test.mjs && node test/react-vite-template-diagnostics.test.mjs && node test/vue-vite-template-dev-loop.test.mjs && node test/package-resolution.test.mjs && node test/build-pipeline.test.mjs && node test/dev-server-preview.test.mjs && node test/terminal-commands.test.mjs && node test/esbuild-wasm-worker.test.mjs && node test/browser-preview-execution.test.mjs && node test/demo-samples.test.mjs",
|
|
38
38
|
"test:browser-preview": "pnpm run test:build && node test/browser-preview-execution.test.mjs",
|
|
39
39
|
"test:demo-samples": "pnpm run test:build && node test/demo-samples.test.mjs",
|
|
40
40
|
"test:static-demo": "node test/static-demo.test.mjs",
|