@openforge-app/plugin-sdk 0.1.0

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.
@@ -0,0 +1,70 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://openforge.dev/schemas/package-openforge.v1.schema.json",
4
+ "title": "OpenForge package metadata",
5
+ "description": "Schema for package.json#openforge metadata used by OpenForge plugin packages.",
6
+ "type": "object",
7
+ "additionalProperties": false,
8
+ "required": ["id", "apiVersion", "displayName", "description"],
9
+ "properties": {
10
+ "id": {
11
+ "type": "string",
12
+ "minLength": 1,
13
+ "pattern": "^[a-z][a-z0-9]*(?:[._-][a-z0-9]+)*$",
14
+ "description": "Explicit app-wide plugin id. Host-exposed contribution ids are qualified with this id."
15
+ },
16
+ "apiVersion": {
17
+ "enum": [1]
18
+ },
19
+ "displayName": {
20
+ "type": "string",
21
+ "minLength": 1
22
+ },
23
+ "description": {
24
+ "type": "string",
25
+ "minLength": 1
26
+ },
27
+ "icon": {
28
+ "type": "string",
29
+ "minLength": 1,
30
+ "description": "Semantic OpenForge icon key or package asset reference."
31
+ },
32
+ "frontend": {
33
+ "type": "string",
34
+ "minLength": 1,
35
+ "description": "Path to the built frontend JavaScript entry artifact."
36
+ },
37
+ "backend": {
38
+ "type": "string",
39
+ "minLength": 1,
40
+ "description": "Path to the built backend JavaScript entry artifact."
41
+ },
42
+ "requires": {
43
+ "type": "array",
44
+ "uniqueItems": true,
45
+ "items": {
46
+ "enum": [
47
+ "commands",
48
+ "events",
49
+ "views",
50
+ "taskPane",
51
+ "settings",
52
+ "background",
53
+ "backend",
54
+ "storage",
55
+ "context",
56
+ "navigation",
57
+ "tasks",
58
+ "projects",
59
+ "fs",
60
+ "shell",
61
+ "notifications",
62
+ "attention",
63
+ "system.openUrl",
64
+ "config",
65
+ "projectConfig"
66
+ ]
67
+ }
68
+ }
69
+ }
70
+ }
@@ -0,0 +1,30 @@
1
+ import { type MergeReadinessAction, type MergeReadinessDetail, type MergeReadinessStatus, type MergeStatusInfo } from './domain';
2
+ export type PrChipSurface = 'compact' | 'detail';
3
+ export type PrChipVariant = 'success' | 'error' | 'pending' | 'muted' | 'neutral' | 'done' | 'merged' | 'closed';
4
+ export type PrChipType = 'draft' | 'ci' | 'review' | 'merge';
5
+ export type PrChipIcon = 'check' | 'cross' | 'clock' | null;
6
+ export interface PrStatusChipSpec {
7
+ type: PrChipType;
8
+ label: string;
9
+ variant: PrChipVariant;
10
+ surface: PrChipSurface;
11
+ icon?: PrChipIcon;
12
+ pulse?: boolean;
13
+ }
14
+ export interface PrInput extends MergeStatusInfo {
15
+ draft?: boolean;
16
+ is_queued?: boolean;
17
+ ci_status?: string | null;
18
+ review_status?: string | null;
19
+ merged_at?: number | null;
20
+ head_sha?: string | null;
21
+ updated_at?: number | null;
22
+ unaddressed_comment_count?: number;
23
+ merge_readiness_status?: MergeReadinessStatus | null;
24
+ merge_readiness_action?: MergeReadinessAction | null;
25
+ merge_readiness_blockers?: string | MergeReadinessDetail[] | null;
26
+ merge_readiness_warnings?: string | MergeReadinessDetail[] | null;
27
+ readiness_source_head_sha?: string | null;
28
+ readiness_updated_at?: number | null;
29
+ }
30
+ export declare function getPrStatusChips(pr: PrInput, surface: PrChipSurface): PrStatusChipSpec[];
@@ -0,0 +1,151 @@
1
+ import { getMergeReadiness, isClosedUnmergedPullRequest, isMergedPullRequest } from './domain';
2
+ export function getPrStatusChips(pr, surface) {
3
+ const chips = [];
4
+ if (pr.draft && pr.state === 'open') {
5
+ chips.push({
6
+ type: 'draft',
7
+ label: 'Draft',
8
+ variant: 'muted',
9
+ surface,
10
+ });
11
+ }
12
+ if (pr.ci_status && pr.ci_status !== 'none' && pr.state === 'open') {
13
+ if (surface === 'compact') {
14
+ const labels = {
15
+ success: 'CI Passed',
16
+ failure: 'CI Failed',
17
+ pending: 'CI Pending',
18
+ };
19
+ chips.push({
20
+ type: 'ci',
21
+ label: labels[pr.ci_status] || pr.ci_status,
22
+ variant: pr.ci_status === 'success' ? 'success' : pr.ci_status === 'failure' ? 'error' : 'pending',
23
+ surface,
24
+ });
25
+ }
26
+ else {
27
+ const labels = {
28
+ success: 'Passing',
29
+ failure: 'Failing',
30
+ pending: 'Running',
31
+ };
32
+ const icons = {
33
+ success: 'check',
34
+ failure: 'cross',
35
+ pending: 'clock',
36
+ };
37
+ chips.push({
38
+ type: 'ci',
39
+ label: labels[pr.ci_status] || pr.ci_status,
40
+ variant: pr.ci_status === 'success' ? 'success' : pr.ci_status === 'failure' ? 'error' : 'pending',
41
+ icon: icons[pr.ci_status] || null,
42
+ surface,
43
+ });
44
+ }
45
+ }
46
+ const normalizedReviewStatus = pr.review_status === 'pending' || pr.review_status === 'review_required'
47
+ ? 'review_required'
48
+ : pr.review_status;
49
+ if (normalizedReviewStatus && normalizedReviewStatus !== 'none' && pr.state === 'open') {
50
+ if (surface === 'compact') {
51
+ const labels = {
52
+ approved: 'Approved',
53
+ changes_requested: 'Changes Req.',
54
+ review_required: 'Needs Review',
55
+ };
56
+ chips.push({
57
+ type: 'review',
58
+ label: labels[normalizedReviewStatus] || normalizedReviewStatus,
59
+ variant: normalizedReviewStatus === 'approved' ? 'success' : normalizedReviewStatus === 'changes_requested' ? 'pending' : 'neutral',
60
+ surface,
61
+ });
62
+ }
63
+ else {
64
+ const labels = {
65
+ approved: 'Approved',
66
+ changes_requested: 'Changes Requested',
67
+ review_required: 'Review Required',
68
+ };
69
+ const icons = {
70
+ approved: 'check',
71
+ changes_requested: 'cross',
72
+ review_required: 'clock',
73
+ };
74
+ chips.push({
75
+ type: 'review',
76
+ label: labels[normalizedReviewStatus] || normalizedReviewStatus,
77
+ variant: normalizedReviewStatus === 'approved' ? 'success' : normalizedReviewStatus === 'changes_requested' ? 'pending' : 'neutral',
78
+ icon: icons[normalizedReviewStatus] || null,
79
+ surface,
80
+ });
81
+ }
82
+ }
83
+ if (isMergedPullRequest(pr)) {
84
+ chips.push({
85
+ type: 'merge',
86
+ label: surface === 'compact' ? 'merged' : 'Merged',
87
+ variant: 'merged',
88
+ icon: surface === 'detail' ? 'check' : undefined,
89
+ surface,
90
+ });
91
+ }
92
+ else if (isClosedUnmergedPullRequest(pr)) {
93
+ chips.push({
94
+ type: 'merge',
95
+ label: surface === 'compact' ? 'closed' : 'Closed',
96
+ variant: 'closed',
97
+ icon: surface === 'detail' ? 'cross' : undefined,
98
+ surface,
99
+ });
100
+ }
101
+ else if (pr.state === 'open') {
102
+ const readiness = getMergeReadiness(pr);
103
+ const hasMergeConflict = readiness.blockers.some((blocker) => blocker.code === 'merge_conflict');
104
+ if (hasMergeConflict) {
105
+ chips.push({
106
+ type: 'merge',
107
+ label: 'Merge Conflict',
108
+ variant: 'error',
109
+ icon: surface === 'detail' ? 'cross' : undefined,
110
+ surface,
111
+ });
112
+ }
113
+ else if (readiness.status === 'queued_pull_request') {
114
+ chips.push({
115
+ type: 'merge',
116
+ label: surface === 'compact' ? 'Queued' : 'Queued Pull Request',
117
+ variant: 'done',
118
+ icon: surface === 'detail' ? 'check' : undefined,
119
+ surface,
120
+ });
121
+ }
122
+ else if (readiness.status === 'ready_to_enqueue') {
123
+ chips.push({
124
+ type: 'merge',
125
+ label: 'Ready to Enqueue',
126
+ variant: 'done',
127
+ icon: surface === 'detail' ? 'check' : undefined,
128
+ surface,
129
+ });
130
+ }
131
+ else if (readiness.status === 'ready_to_merge') {
132
+ chips.push({
133
+ type: 'merge',
134
+ label: 'Ready to Merge',
135
+ variant: 'done',
136
+ icon: surface === 'detail' ? 'check' : undefined,
137
+ surface,
138
+ });
139
+ }
140
+ else if (readiness.status === 'readiness_unknown') {
141
+ chips.push({
142
+ type: 'merge',
143
+ label: 'Readiness Unknown',
144
+ variant: 'neutral',
145
+ icon: surface === 'detail' ? 'clock' : undefined,
146
+ surface,
147
+ });
148
+ }
149
+ }
150
+ return chips;
151
+ }
@@ -0,0 +1,68 @@
1
+ export interface ProjectFileTreeEntry {
2
+ name: string;
3
+ path: string;
4
+ isDir: boolean;
5
+ size: number | null;
6
+ }
7
+ export interface ProjectFileTreeNode<Entry extends ProjectFileTreeEntry = ProjectFileTreeEntry> {
8
+ entry: Entry;
9
+ children: ProjectFileTreeNode<Entry>[];
10
+ level: number;
11
+ parentPath: string | null;
12
+ posInSet: number;
13
+ setSize: number;
14
+ }
15
+ export type ProjectFileTreeKeyboardAction = {
16
+ handled: false;
17
+ } | {
18
+ handled: true;
19
+ type: 'activate';
20
+ path: string;
21
+ } | {
22
+ handled: true;
23
+ type: 'focus';
24
+ path: string;
25
+ } | {
26
+ handled: true;
27
+ type: 'toggle';
28
+ path: string;
29
+ } | {
30
+ handled: true;
31
+ type: 'none';
32
+ };
33
+ export interface ProjectFileTreeItemAccessibility {
34
+ level: number;
35
+ setSize: number;
36
+ posInSet: number;
37
+ expanded: boolean | undefined;
38
+ current: 'true' | undefined;
39
+ selected: 'true' | 'false' | undefined;
40
+ labelledBy: string;
41
+ }
42
+ interface ProjectFileTreeItemAccessibilityState {
43
+ expandedDirs: ReadonlySet<string>;
44
+ selectedPath: string | null;
45
+ labelId: string;
46
+ sizeId: string;
47
+ }
48
+ interface ProjectFileTreeKeyboardState {
49
+ expandedDirs: ReadonlySet<string>;
50
+ visiblePaths: readonly string[];
51
+ }
52
+ interface ProjectFileTreeKeyboardEventLike {
53
+ key: string;
54
+ altKey: boolean;
55
+ ctrlKey: boolean;
56
+ metaKey: boolean;
57
+ shiftKey: boolean;
58
+ }
59
+ export declare function getProjectFileTreeDepth(path: string): number;
60
+ export declare function getProjectFileTreeParentPath(path: string): string | null;
61
+ export declare function buildProjectFileTree<Entry extends ProjectFileTreeEntry>(flatEntries: readonly Entry[]): ProjectFileTreeNode<Entry>[];
62
+ export declare function flattenVisibleProjectFileTree<Entry extends ProjectFileTreeEntry>(nodes: readonly ProjectFileTreeNode<Entry>[], expandedDirs: ReadonlySet<string>): ProjectFileTreeNode<Entry>[];
63
+ export declare function getProjectFileTreeItemAccessibility<Entry extends ProjectFileTreeEntry>(node: ProjectFileTreeNode<Entry>, state: ProjectFileTreeItemAccessibilityState): ProjectFileTreeItemAccessibility;
64
+ export declare function formatProjectFileTreeSize(size: number | null): string;
65
+ export declare function projectFileTreePathToId(path: string): string;
66
+ export declare function hasProjectFileTreeShortcutModifier(event: ProjectFileTreeKeyboardEventLike): boolean;
67
+ export declare function getProjectFileTreeKeyboardAction<Entry extends ProjectFileTreeEntry>(event: ProjectFileTreeKeyboardEventLike, node: ProjectFileTreeNode<Entry>, state: ProjectFileTreeKeyboardState): ProjectFileTreeKeyboardAction;
68
+ export {};
@@ -0,0 +1,141 @@
1
+ export function getProjectFileTreeDepth(path) {
2
+ return path.split('/').length - 1;
3
+ }
4
+ export function getProjectFileTreeParentPath(path) {
5
+ const lastSlash = path.lastIndexOf('/');
6
+ return lastSlash === -1 ? null : path.slice(0, lastSlash);
7
+ }
8
+ export function buildProjectFileTree(flatEntries) {
9
+ const nodesByPath = new Map();
10
+ const roots = [];
11
+ for (const entry of flatEntries) {
12
+ nodesByPath.set(entry.path, {
13
+ entry,
14
+ children: [],
15
+ level: 1,
16
+ parentPath: getProjectFileTreeParentPath(entry.path),
17
+ posInSet: 1,
18
+ setSize: 1,
19
+ });
20
+ }
21
+ for (const entry of flatEntries) {
22
+ const node = nodesByPath.get(entry.path);
23
+ if (!node)
24
+ continue;
25
+ const parent = node.parentPath ? nodesByPath.get(node.parentPath) : null;
26
+ if (parent) {
27
+ parent.children.push(node);
28
+ }
29
+ else {
30
+ roots.push(node);
31
+ }
32
+ }
33
+ assignProjectFileTreeMetadata(roots, 1);
34
+ return roots;
35
+ }
36
+ function assignProjectFileTreeMetadata(nodes, level) {
37
+ const setSize = nodes.length;
38
+ nodes.forEach((node, index) => {
39
+ node.level = level;
40
+ node.posInSet = index + 1;
41
+ node.setSize = setSize;
42
+ assignProjectFileTreeMetadata(node.children, level + 1);
43
+ });
44
+ }
45
+ export function flattenVisibleProjectFileTree(nodes, expandedDirs) {
46
+ const result = [];
47
+ function visit(items) {
48
+ for (const item of items) {
49
+ result.push(item);
50
+ if (item.entry.isDir && expandedDirs.has(item.entry.path)) {
51
+ visit(item.children);
52
+ }
53
+ }
54
+ }
55
+ visit(nodes);
56
+ return result;
57
+ }
58
+ export function getProjectFileTreeItemAccessibility(node, state) {
59
+ const isSelectedFile = !node.entry.isDir && state.selectedPath === node.entry.path;
60
+ return {
61
+ level: node.level,
62
+ setSize: node.setSize,
63
+ posInSet: node.posInSet,
64
+ expanded: node.entry.isDir ? state.expandedDirs.has(node.entry.path) : undefined,
65
+ current: isSelectedFile ? 'true' : undefined,
66
+ selected: !node.entry.isDir ? (isSelectedFile ? 'true' : 'false') : undefined,
67
+ labelledBy: !node.entry.isDir && node.entry.size !== null ? `${state.labelId} ${state.sizeId}` : state.labelId,
68
+ };
69
+ }
70
+ export function formatProjectFileTreeSize(size) {
71
+ if (size === null)
72
+ return '';
73
+ if (size < 1024)
74
+ return `${size} B`;
75
+ if (size < 1024 * 1024)
76
+ return `${(size / 1024).toFixed(1)} KB`;
77
+ return `${(size / (1024 * 1024)).toFixed(1)} MB`;
78
+ }
79
+ export function projectFileTreePathToId(path) {
80
+ return `project-file-tree-${Array.from(path).map((char) => char.charCodeAt(0).toString(36)).join('-')}`;
81
+ }
82
+ export function hasProjectFileTreeShortcutModifier(event) {
83
+ return event.altKey || event.ctrlKey || event.metaKey || event.shiftKey;
84
+ }
85
+ export function getProjectFileTreeKeyboardAction(event, node, state) {
86
+ if (hasProjectFileTreeShortcutModifier(event))
87
+ return { handled: false };
88
+ switch (event.key) {
89
+ case 'ArrowDown':
90
+ return focusByOffset(node.entry.path, state.visiblePaths, 1);
91
+ case 'ArrowUp':
92
+ return focusByOffset(node.entry.path, state.visiblePaths, -1);
93
+ case 'Home':
94
+ return focusFirst(state.visiblePaths);
95
+ case 'End':
96
+ return focusLast(state.visiblePaths);
97
+ case 'ArrowRight':
98
+ return getArrowRightAction(node, state.expandedDirs);
99
+ case 'ArrowLeft':
100
+ return getArrowLeftAction(node, state.expandedDirs, state.visiblePaths);
101
+ case 'Enter':
102
+ case ' ':
103
+ return { handled: true, type: 'activate', path: node.entry.path };
104
+ default:
105
+ return { handled: false };
106
+ }
107
+ }
108
+ function focusByOffset(currentPath, visiblePaths, offset) {
109
+ const currentIndex = visiblePaths.indexOf(currentPath);
110
+ if (currentIndex === -1)
111
+ return { handled: true, type: 'none' };
112
+ const nextIndex = Math.max(0, Math.min(visiblePaths.length - 1, currentIndex + offset));
113
+ const nextPath = visiblePaths[nextIndex];
114
+ return nextPath ? { handled: true, type: 'focus', path: nextPath } : { handled: true, type: 'none' };
115
+ }
116
+ function focusFirst(visiblePaths) {
117
+ const firstPath = visiblePaths[0];
118
+ return firstPath ? { handled: true, type: 'focus', path: firstPath } : { handled: true, type: 'none' };
119
+ }
120
+ function focusLast(visiblePaths) {
121
+ const lastPath = visiblePaths.at(-1);
122
+ return lastPath ? { handled: true, type: 'focus', path: lastPath } : { handled: true, type: 'none' };
123
+ }
124
+ function getArrowRightAction(node, expandedDirs) {
125
+ if (!node.entry.isDir)
126
+ return { handled: true, type: 'none' };
127
+ if (!expandedDirs.has(node.entry.path)) {
128
+ return { handled: true, type: 'toggle', path: node.entry.path };
129
+ }
130
+ const firstChild = node.children[0];
131
+ return firstChild ? { handled: true, type: 'focus', path: firstChild.entry.path } : { handled: true, type: 'none' };
132
+ }
133
+ function getArrowLeftAction(node, expandedDirs, visiblePaths) {
134
+ if (node.entry.isDir && expandedDirs.has(node.entry.path)) {
135
+ return { handled: true, type: 'toggle', path: node.entry.path };
136
+ }
137
+ if (node.parentPath && visiblePaths.includes(node.parentPath)) {
138
+ return { handled: true, type: 'focus', path: node.parentPath };
139
+ }
140
+ return { handled: true, type: 'none' };
141
+ }
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Sanitize HTML to prevent XSS attacks.
3
+ * Strips all dangerous tags (script, iframe, etc.) and event handlers.
4
+ * Allows safe structural/formatting HTML through.
5
+ */
6
+ export declare function sanitizeHtml(dirty: string): string;
@@ -0,0 +1,13 @@
1
+ import DOMPurify from 'dompurify';
2
+ /**
3
+ * Sanitize HTML to prevent XSS attacks.
4
+ * Strips all dangerous tags (script, iframe, etc.) and event handlers.
5
+ * Allows safe structural/formatting HTML through.
6
+ */
7
+ export function sanitizeHtml(dirty) {
8
+ return DOMPurify.sanitize(dirty, {
9
+ USE_PROFILES: { html: true },
10
+ FORBID_TAGS: ['style'],
11
+ FORBID_ATTR: ['style'],
12
+ });
13
+ }
@@ -0,0 +1,18 @@
1
+ export type SvelteHostRuntimeModule = Readonly<{
2
+ specifier: string
3
+ sourcePath: string
4
+ assetPath: string
5
+ }>
6
+
7
+ export const SVELTE_HOST_RUNTIME_MODULES: readonly SvelteHostRuntimeModule[]
8
+ export const OPENFORGE_HOST_RUNTIME_SVELTE_SPECIFIERS: readonly string[]
9
+ export const SVELTE_HOST_RUNTIME_IMPORTS: Readonly<Record<string, string>>
10
+ export function svelteHostRuntimeImportUrl(specifier: string): string | null
11
+ export function svelteHostRuntimeBuildEntries(): Record<string, string>
12
+ export function svelteHostRuntimeImportMapEntries(): Record<string, string>
13
+ export function terminalRuntimeImportMapEntries(): Record<string, string>
14
+ export function rendererImportMapEntries(): Record<string, string>
15
+ export function rendererImportMapScriptBody(): string
16
+ export function rendererImportMapScriptSha256(): string
17
+ export function rendererImportMapScriptHashSource(): string
18
+ export function rendererImportMapHtml(): string
@@ -0,0 +1,81 @@
1
+ import { createHash } from 'node:crypto'
2
+
3
+ const HOST_RUNTIME_SVELTE_BASE_URL = 'plugin://host-runtime/svelte/'
4
+
5
+ export const SVELTE_HOST_RUNTIME_MODULES = Object.freeze([
6
+ { specifier: 'svelte', sourcePath: 'index-client.js', assetPath: 'index.js' },
7
+ { specifier: 'svelte/animate', sourcePath: 'animate/index.js', assetPath: 'animate.js' },
8
+ { specifier: 'svelte/attachments', sourcePath: 'attachments/index.js', assetPath: 'attachments.js' },
9
+ { specifier: 'svelte/easing', sourcePath: 'easing/index.js', assetPath: 'easing.js' },
10
+ { specifier: 'svelte/events', sourcePath: 'events/index.js', assetPath: 'events.js' },
11
+ { specifier: 'svelte/internal', sourcePath: 'internal/index.js', assetPath: 'internal.js' },
12
+ { specifier: 'svelte/internal/client', sourcePath: 'internal/client/index.js', assetPath: 'internal/client/index.js' },
13
+ { specifier: 'svelte/internal/disclose-version', sourcePath: 'internal/disclose-version.js', assetPath: 'internal/disclose-version.js' },
14
+ { specifier: 'svelte/internal/flags/async', sourcePath: 'internal/flags/async.js', assetPath: 'internal/flags/async.js' },
15
+ { specifier: 'svelte/internal/flags/legacy', sourcePath: 'internal/flags/legacy.js', assetPath: 'internal/flags/legacy.js' },
16
+ { specifier: 'svelte/internal/flags/tracing', sourcePath: 'internal/flags/tracing.js', assetPath: 'internal/flags/tracing.js' },
17
+ { specifier: 'svelte/legacy', sourcePath: 'legacy/legacy-client.js', assetPath: 'legacy.js' },
18
+ { specifier: 'svelte/motion', sourcePath: 'motion/index.js', assetPath: 'motion.js' },
19
+ { specifier: 'svelte/reactivity', sourcePath: 'reactivity/index-client.js', assetPath: 'reactivity.js' },
20
+ { specifier: 'svelte/reactivity/window', sourcePath: 'reactivity/window/index.js', assetPath: 'reactivity/window/index.js' },
21
+ { specifier: 'svelte/store', sourcePath: 'store/index-client.js', assetPath: 'store.js' },
22
+ { specifier: 'svelte/transition', sourcePath: 'transition/index.js', assetPath: 'transition.js' },
23
+ ])
24
+
25
+ export const OPENFORGE_HOST_RUNTIME_SVELTE_SPECIFIERS = Object.freeze(
26
+ SVELTE_HOST_RUNTIME_MODULES.map(module => module.specifier),
27
+ )
28
+
29
+ export const SVELTE_HOST_RUNTIME_IMPORTS = Object.freeze(Object.fromEntries(
30
+ SVELTE_HOST_RUNTIME_MODULES.map(module => [module.specifier, `${HOST_RUNTIME_SVELTE_BASE_URL}${module.assetPath}`]),
31
+ ))
32
+
33
+ export function svelteHostRuntimeImportUrl(specifier) {
34
+ return SVELTE_HOST_RUNTIME_IMPORTS[specifier] ?? null
35
+ }
36
+
37
+ export function svelteHostRuntimeBuildEntries() {
38
+ return Object.fromEntries(
39
+ SVELTE_HOST_RUNTIME_MODULES.map(module => [module.assetPath.replace(/\.js$/, ''), module.sourcePath]),
40
+ )
41
+ }
42
+
43
+ export function svelteHostRuntimeImportMapEntries() {
44
+ return { ...SVELTE_HOST_RUNTIME_IMPORTS }
45
+ }
46
+
47
+ export function terminalRuntimeImportMapEntries() {
48
+ return {
49
+ '@openforge-app/terminal-runtime': 'plugin://host-runtime/terminal-runtime/index.js',
50
+ '@openforge-app/terminal-runtime/terminalRuntime': 'plugin://host-runtime/terminal-runtime/terminalRuntime.js',
51
+ '@openforge-app/terminal-runtime/terminalOptions': 'plugin://host-runtime/terminal-runtime/terminalOptions.js',
52
+ '@openforge-app/terminal-runtime/theme': 'plugin://host-runtime/terminal-runtime/theme.js',
53
+ '@openforge-app/terminal-runtime/shortcuts': 'plugin://host-runtime/terminal-runtime/shortcuts.js',
54
+ '@openforge-app/terminal-runtime/shortcutController': 'plugin://host-runtime/terminal-runtime/shortcutController.js',
55
+ '@openforge-app/terminal-runtime/TerminalTabsShell': 'plugin://host-runtime/terminal-runtime/TerminalTabsShell.js',
56
+ }
57
+ }
58
+
59
+ export function rendererImportMapEntries() {
60
+ return {
61
+ ...svelteHostRuntimeImportMapEntries(),
62
+ '@openforge-app/plugin-sdk': 'plugin://host-runtime/plugin-sdk/index.js',
63
+ ...terminalRuntimeImportMapEntries(),
64
+ }
65
+ }
66
+
67
+ export function rendererImportMapScriptBody() {
68
+ return `\n${JSON.stringify({ imports: rendererImportMapEntries() }, null, 2)}\n`
69
+ }
70
+
71
+ export function rendererImportMapScriptSha256() {
72
+ return `sha256-${createHash('sha256').update(rendererImportMapScriptBody(), 'utf8').digest('base64')}`
73
+ }
74
+
75
+ export function rendererImportMapScriptHashSource() {
76
+ return `'${rendererImportMapScriptSha256()}'`
77
+ }
78
+
79
+ export function rendererImportMapHtml() {
80
+ return `<script type="importmap">${rendererImportMapScriptBody()}</script>`
81
+ }