@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.
Files changed (50) hide show
  1. package/README.md +8 -4
  2. package/dist/build-pipeline/browser-bundle.d.ts +2 -1
  3. package/dist/build-pipeline/browser-bundle.js +384 -24
  4. package/dist/build-pipeline/diagnostics.d.ts +1 -0
  5. package/dist/build-pipeline/diagnostics.js +11 -2
  6. package/dist/build-pipeline/index.d.ts +2 -2
  7. package/dist/build-pipeline/index.js +2 -1
  8. package/dist/build-pipeline/pipeline.d.ts +2 -0
  9. package/dist/build-pipeline/pipeline.js +149 -28
  10. package/dist/build-pipeline/types.d.ts +24 -2
  11. package/dist/build-pipeline/vite-config.d.ts +8 -0
  12. package/dist/build-pipeline/vite-config.js +215 -0
  13. package/dist/dev-server-preview/index.d.ts +2 -2
  14. package/dist/dev-server-preview/index.js +2 -1
  15. package/dist/dev-server-preview/preview-document.d.ts +2 -1
  16. package/dist/dev-server-preview/preview-document.js +203 -1
  17. package/dist/dev-server-preview/server.d.ts +8 -1
  18. package/dist/dev-server-preview/server.js +248 -33
  19. package/dist/dev-server-preview/types.d.ts +40 -1
  20. package/dist/forge-controller.js +1 -0
  21. package/dist/index.d.ts +5 -4
  22. package/dist/index.js +16 -2
  23. package/dist/package-resolution/diagnostics.d.ts +1 -0
  24. package/dist/package-resolution/diagnostics.js +10 -1
  25. package/dist/package-resolution/errors.d.ts +2 -1
  26. package/dist/package-resolution/errors.js +2 -1
  27. package/dist/package-resolution/index.d.ts +2 -2
  28. package/dist/package-resolution/index.js +2 -1
  29. package/dist/package-resolution/resolver.d.ts +3 -0
  30. package/dist/package-resolution/resolver.js +123 -29
  31. package/dist/package-resolution/types.d.ts +7 -1
  32. package/dist/telemetry/index.d.ts +68 -0
  33. package/dist/telemetry/index.js +65 -0
  34. package/dist/terminal-commands/npm-build.js +3 -1
  35. package/dist/terminal-commands/npm-dev.js +3 -1
  36. package/dist/terminal-commands/npm-install.js +7 -2
  37. package/dist/terminal-commands/terminal.d.ts +1 -0
  38. package/dist/terminal-commands/terminal.js +1 -0
  39. package/dist/terminal-commands/types.d.ts +2 -0
  40. package/dist/virtual-file-system/diagnostics.d.ts +1 -0
  41. package/dist/virtual-file-system/diagnostics.js +9 -1
  42. package/dist/virtual-file-system/index.d.ts +4 -2
  43. package/dist/virtual-file-system/index.js +7 -1
  44. package/dist/virtual-file-system/snapshot-serialization.d.ts +3 -0
  45. package/dist/virtual-file-system/snapshot-serialization.js +242 -0
  46. package/dist/virtual-file-system/storage.d.ts +1 -1
  47. package/dist/virtual-file-system/storage.js +41 -18
  48. package/dist/virtual-file-system/types.d.ts +28 -1
  49. package/dist/virtual-file-system/types.js +2 -0
  50. 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;
@@ -42,6 +42,7 @@ class DefaultForgeTerminal {
42
42
  buildPipeline: this.options.buildPipeline,
43
43
  devServerPreview: this.options.devServerPreview,
44
44
  packageResolver: this.options.packageResolver,
45
+ signal: executeOptions.signal,
45
46
  });
46
47
  }
47
48
  }
@@ -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 type { ForgeBuildCacheRecord, ForgeDeleteOptions, ForgeDiagnostic, ForgeDiagnosticCode, ForgeDiagnosticSeverity, ForgeDirEntry, ForgeFileCreatedEvent, ForgeFileDeletedEvent, ForgeFileEvent, ForgeFileKind, ForgeFileRecord, ForgeFileRenamedEvent, ForgeFileSource, ForgeFileStat, ForgeFileUpdatedEvent, ForgeFileWatchListener, ForgeLimitDiagnostic, ForgeMkdirOptions, ForgeOpenVirtualFileSystemOptions, ForgePackageCacheRecord, ForgePersistedWorkspace, ForgeVirtualFileSystemLimits, ResolvedForgeVirtualFileSystemLimits, ForgeRestoreOptions, ForgeSnapshot, ForgeSnapshotOptions, ForgeUnsubscribe, ForgeVirtualFileSystemOptions, ForgeVirtualFileSystemStorage, ForgeWorkspace, ForgeWorkspaceMetadata, ForgeWriteFileOptions, } from "./types";
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 assertCacheLimit;
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).put(record);
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
- async assertCacheLimit(storeName, record) {
110
- const limitName = storeName === exports.FORGE_INDEXED_DB_STORES.packageCache
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;
@@ -1,2 +1,4 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.FORGE_WORKSPACE_SNAPSHOT_EXPORT_SCHEMA_VERSION = void 0;
4
+ exports.FORGE_WORKSPACE_SNAPSHOT_EXPORT_SCHEMA_VERSION = 1;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@caryhu/codemine-forge",
3
- "version": "0.0.1-alpha.0",
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",