Edited Spark

This commit is contained in:
2026-01-17 22:16:42 +00:00
committed by GitHub
parent 6d5aa6140c
commit 9652993f66
60 changed files with 108024 additions and 14 deletions

View File

@@ -0,0 +1,21 @@
MIT License
Copyright GitHub, Inc.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@@ -0,0 +1,9 @@
import type { PluginOption } from 'vite';
interface Opts {
serverURL?: string;
disabled?: boolean;
maxRetries?: number;
retryDelay?: number;
}
export default function sparkAgent(opts?: Opts): PluginOption;
export {};

123
packages/spark-tools/dist/agentPlugin.js vendored Normal file
View File

@@ -0,0 +1,123 @@
function sparkAgent(opts = {}) {
const serverURL = opts.serverURL || 'http://localhost:9000';
const disabled = opts.disabled || false;
const maxRetries = opts.maxRetries || 5;
const retryDelay = opts.retryDelay || 1000; // ms
async function sendEvent(event, retries = 0) {
if (disabled)
return true;
try {
const res = await fetch(`${serverURL}/notify`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(event),
});
if (!res.ok) {
console.warn('Failed to send event to Spark Agent:', res.status, res.statusText);
if (retries < maxRetries) {
console.log(`Retrying event delivery (attempt ${retries + 1}/${maxRetries})...`);
await new Promise((resolve) => setTimeout(resolve, retryDelay));
return sendEvent(event, retries + 1);
}
return false;
}
return true;
}
catch (err) {
console.warn('Failed to send event to Spark Agent:', err);
if (retries < maxRetries) {
console.log(`Retrying event delivery (attempt ${retries + 1}/${maxRetries})...`);
await new Promise((resolve) => setTimeout(resolve, retryDelay));
return sendEvent(event, retries + 1);
}
return false;
}
}
function sendStartedEvent(file) {
return sendEvent({ type: 'build:started', timestamp: new Date().getTime(), details: { file } });
}
function sendSuccessEvent(file) {
return sendEvent({ type: 'build:success', timestamp: new Date().getTime(), details: { file } });
}
function sendErrorEvent(error) {
const event = {
type: 'build:failed',
timestamp: new Date().getTime(),
details: {
error: {
message: error || 'Unknown error',
},
},
};
return sendEvent(event);
}
if (disabled) {
return { name: 'spark-agent:disabled', apply: 'build' };
}
return {
name: 'spark-agent',
apply: 'serve',
configureServer(server) {
server.watcher.on('change', (file) => {
sendStartedEvent(file);
});
const wss = server.ws.send;
server.ws.send = function (payload) {
if (payload.type === 'update') {
const file = payload.updates[0]?.path;
sendSuccessEvent(file);
}
else if (payload.type === 'full-reload') {
// Certain error corrections may trigger a full-reload, so we need to send success.
// Vite may trigger a full-reload while we still have errors, but if so
// we expect this event will be followed with an error notification.
const file = payload.triggeredBy;
sendSuccessEvent(file);
}
else if (payload.type === 'error') {
let errorMessage;
if (payload.err) {
try {
const parsedError = JSON.parse(JSON.stringify(payload.err));
errorMessage = [
parsedError.message,
parsedError.frame,
`at ${parsedError.id}:${parsedError.loc?.line}:${parsedError.loc?.column}`,
]
.filter(Boolean)
.join('\n');
}
catch {
errorMessage = JSON.stringify(payload.err);
}
}
else {
errorMessage =
payload.error?.stack ||
payload.error?.message ||
(typeof payload.error === 'string' ? payload.error : JSON.stringify(payload.error)) ||
'Unknown error';
}
sendErrorEvent(errorMessage);
}
return wss.call(this, payload);
};
},
buildStart() {
sendStartedEvent();
},
buildEnd(err) {
if (err) {
sendErrorEvent(err.message);
}
else {
sendSuccessEvent();
}
},
};
}
export { sparkAgent as default };
//# sourceMappingURL=agentPlugin.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"agentPlugin.js","sources":["../src/agentPlugin.ts"],"sourcesContent":[null],"names":[],"mappings":"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"}

329
packages/spark-tools/dist/db.js vendored Normal file
View File

@@ -0,0 +1,329 @@
import { K as KVClient } from './kv-DBiZoNWq.js';
// These values should NEVER change. The values are precisely for
// generating ULIDs.
const ENCODING = "0123456789ABCDEFGHJKMNPQRSTVWXYZ"; // Crockford's Base32
const ENCODING_LEN = 32; // from ENCODING.length;
const RANDOM_LEN = 16;
const TIME_LEN = 10;
const TIME_MAX = 281474976710655; // from Math.pow(2, 48) - 1;
var ULIDErrorCode;
(function (ULIDErrorCode) {
ULIDErrorCode["Base32IncorrectEncoding"] = "B32_ENC_INVALID";
ULIDErrorCode["DecodeTimeInvalidCharacter"] = "DEC_TIME_CHAR";
ULIDErrorCode["DecodeTimeValueMalformed"] = "DEC_TIME_MALFORMED";
ULIDErrorCode["EncodeTimeNegative"] = "ENC_TIME_NEG";
ULIDErrorCode["EncodeTimeSizeExceeded"] = "ENC_TIME_SIZE_EXCEED";
ULIDErrorCode["EncodeTimeValueMalformed"] = "ENC_TIME_MALFORMED";
ULIDErrorCode["PRNGDetectFailure"] = "PRNG_DETECT";
ULIDErrorCode["ULIDInvalid"] = "ULID_INVALID";
ULIDErrorCode["Unexpected"] = "UNEXPECTED";
ULIDErrorCode["UUIDInvalid"] = "UUID_INVALID";
})(ULIDErrorCode || (ULIDErrorCode = {}));
class ULIDError extends Error {
constructor(errorCode, message) {
super(`${message} (${errorCode})`);
this.name = "ULIDError";
this.code = errorCode;
}
}
function randomChar(prng) {
// Currently PRNGs generate fractions from 0 to _less than_ 1, so no "%" is necessary.
// However, just in case a future PRNG can generate 1,
// we are applying "% ENCODING LEN" to wrap back to the first character
const randomPosition = Math.floor(prng() * ENCODING_LEN) % ENCODING_LEN;
return ENCODING.charAt(randomPosition);
}
/**
* Detect the best PRNG (pseudo-random number generator)
* @param root The root to check from (global/window)
* @returns The PRNG function
*/
function detectPRNG(root) {
const rootLookup = detectRoot();
const globalCrypto = (rootLookup && (rootLookup.crypto || rootLookup.msCrypto)) ||
(null);
if (typeof globalCrypto?.getRandomValues === "function") {
return () => {
const buffer = new Uint8Array(1);
globalCrypto.getRandomValues(buffer);
return buffer[0] / 256;
};
}
else if (typeof globalCrypto?.randomBytes === "function") {
return () => globalCrypto.randomBytes(1).readUInt8() / 256;
}
else ;
throw new ULIDError(ULIDErrorCode.PRNGDetectFailure, "Failed to find a reliable PRNG");
}
function detectRoot() {
if (inWebWorker())
return self;
if (typeof window !== "undefined") {
return window;
}
if (typeof global !== "undefined") {
return global;
}
if (typeof globalThis !== "undefined") {
return globalThis;
}
return null;
}
function encodeRandom(len, prng) {
let str = "";
for (; len > 0; len--) {
str = randomChar(prng) + str;
}
return str;
}
/**
* Encode the time portion of a ULID
* @param now The current timestamp
* @param len Length to generate
* @returns The encoded time
*/
function encodeTime(now, len = TIME_LEN) {
if (isNaN(now)) {
throw new ULIDError(ULIDErrorCode.EncodeTimeValueMalformed, `Time must be a number: ${now}`);
}
else if (now > TIME_MAX) {
throw new ULIDError(ULIDErrorCode.EncodeTimeSizeExceeded, `Cannot encode a time larger than ${TIME_MAX}: ${now}`);
}
else if (now < 0) {
throw new ULIDError(ULIDErrorCode.EncodeTimeNegative, `Time must be positive: ${now}`);
}
else if (Number.isInteger(now) === false) {
throw new ULIDError(ULIDErrorCode.EncodeTimeValueMalformed, `Time must be an integer: ${now}`);
}
let mod, str = "";
for (let currentLen = len; currentLen > 0; currentLen--) {
mod = now % ENCODING_LEN;
str = ENCODING.charAt(mod) + str;
now = (now - mod) / ENCODING_LEN;
}
return str;
}
function inWebWorker() {
// @ts-ignore
return typeof WorkerGlobalScope !== "undefined" && self instanceof WorkerGlobalScope;
}
/**
* Generate a ULID
* @param seedTime Optional time seed
* @param prng Optional PRNG function
* @returns A ULID string
* @example
* ulid(); // "01HNZXD07M5CEN5XA66EMZSRZW"
*/
function ulid(seedTime, prng) {
const currentPRNG = detectPRNG();
const seed = Date.now() ;
return encodeTime(seed, TIME_LEN) + encodeRandom(RANDOM_LEN, currentPRNG);
}
const BASE_DB_SERVICE_URL = '/_spark/db';
/**
* DBClient provides methods to interact with Spark's document database.
*/
class DBClient {
kv;
constructor(kvClient) {
this.kv = kvClient || new KVClient();
}
/**
* Generate a unique document ID using ULID
* @returns A unique document ID
*/
generateDocId() {
return ulid();
}
/**
* Get all documents in a collection using the DB API
* @param collectionName The name of the collection
* @returns Array of all documents in the collection with id field
*/
async getAll(collectionName) {
try {
const response = await fetch(`${BASE_DB_SERVICE_URL}/collections/${collectionName}`, {
method: 'GET',
});
if (!response.ok) {
const errorMessage = `Failed to fetch DB collection: ${response.statusText}`;
throw new Error(errorMessage);
}
let json;
try {
json = await response.json();
}
catch (error) {
const errorMessage = 'Failed to parse DB collection response';
throw new Error(errorMessage);
}
if (!Array.isArray(json)) {
const errorMessage = 'DB collection response is not an array';
throw new Error(errorMessage);
}
const entries = json;
return entries
.map((entry) => {
return { _id: entry.key, ...JSON.parse(entry.value) };
});
}
catch (error) {
console.error(`Error getting collection ${collectionName}:`, error);
return [];
}
}
/**
* Insert a document into a collection with schema validation
* @param collectionName The name of the collection
* @param schema The Zod schema for validation
* @param data The document data to insert
* @returns The inserted document with generated id
*/
async insert(collectionName, schema, data) {
const id = this.generateDocId();
const { _id: _, ...dataWithoutId } = data;
const validatedData = schema.parse(dataWithoutId);
await this.kv.setKey(id, validatedData, collectionName);
return { ...validatedData, _id: id };
}
/**
* Get a document by ID from a collection
* @param collectionName The name of the collection
* @param id The document ID
* @returns The document with id field or null if not found
*/
async get(collectionName, id) {
try {
const doc = await this.kv.getKey(id, collectionName);
if (!doc)
return null;
return { ...doc, _id: id };
}
catch (error) {
console.error(`Error getting document ${id} from ${collectionName}:`, error);
return null;
}
}
/**
* Update a document with partial data and schema validation
* @param collectionName The name of the collection
* @param id The document ID
* @param schema The Zod schema for validation
* @param data Partial data to update
* @returns The updated document or null if not found
*/
async update(collectionName, id, schema, data) {
try {
const existing = await this.kv.getKey(id, collectionName);
if (!existing)
return null;
const { _id: _, ...dataWithoutId } = data;
const updated = { ...existing, ...dataWithoutId };
const validatedData = schema.parse(updated);
await this.kv.setKey(id, validatedData, collectionName);
return { ...validatedData, _id: id };
}
catch (error) {
console.error(`Error updating document ${id} in ${collectionName}:`, error);
return null;
}
}
/**
* Delete a document from a collection
* @param collectionName The name of the collection
* @param id The document ID to delete
* @returns true if document was deleted, false if not found
*/
async delete(collectionName, id) {
try {
await this.kv.deleteKey(id, collectionName);
return true;
}
catch (error) {
console.error(`Error deleting document ${id} from ${collectionName}:`, error);
return false;
}
}
/**
* Query documents with filtering
* @param collectionName The name of the collection
* @param filterFn Function to filter documents
* @returns Array of filtered documents
*/
async query(collectionName, filterFn) {
const collection = await this.getAll(collectionName);
return collection.filter(filterFn);
}
}
const db = new DBClient();
/**
* Create a collection instance with schema validation and clean API
* @param schema The Zod schema for the collection
* @param collectionName The name of the collection
* @returns Collection instance with CRUD operations
*/
function collection(schema, collectionName) {
return {
async insert(data) {
return db.insert(collectionName, schema, data);
},
async get(id) {
return db.get(collectionName, id);
},
async update(id, data) {
return db.update(collectionName, id, schema, data);
},
async delete(id) {
return db.delete(collectionName, id);
},
async getAll() {
return db.getAll(collectionName);
},
async query(options) {
// Get all documents first
let results = await db.getAll(collectionName);
// Apply where condition
if (options?.where) {
const condition = options.where;
results = results.filter((doc) => {
const fieldValue = doc[condition.field];
switch (condition.operator) {
case '==': return fieldValue === condition.value;
case '!=': return fieldValue !== condition.value;
case '>': return fieldValue > condition.value;
case '<': return fieldValue < condition.value;
case '>=': return fieldValue >= condition.value;
case '<=': return fieldValue <= condition.value;
default: return false;
}
});
}
// Apply sorting
if (options?.sortBy) {
results.sort((a, b) => {
const aVal = a[options.sortBy.field];
const bVal = b[options.sortBy.field];
if (aVal < bVal)
return options.sortBy.direction === 'asc' ? -1 : 1;
if (aVal > bVal)
return options.sortBy.direction === 'asc' ? 1 : -1;
return 0;
});
}
// Apply limit
if (options?.limit !== undefined) {
results = results.slice(0, options.limit);
}
return results;
}
};
}
export { DBClient as DB, collection };
//# sourceMappingURL=db.js.map

1
packages/spark-tools/dist/db.js.map vendored Normal file

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,27 @@
.debugger-overlay {
z-index: 1000000;
}
.debugger-overlay[data-element-name]::before {
content: attr(data-element-name);
display: var(--display-tag, none);
justify-content: center;
align-items: center;
position: absolute;
top: -1px;
left: -1px;
transform: translateY(-100%);
background-color: var(--fg-color);
color: white;
border-radius: 4px 4px 0 0;
border: 1px solid var(--fg-color);
border-bottom: none;
font-size: 11px;
font-weight: 400;
line-height: 1.5;
padding: 0px 6px 2px;
}
[contenteditable='true']:focus {
outline: none;
}

View File

@@ -0,0 +1 @@
export {};

View File

@@ -0,0 +1,601 @@
/**
* typed function to send messages to the parent window
*/
function sendMessageToBridge(message) {
window.parent.postMessage(message, '*');
}
let currentSelectedElement = null;
let currentHighlightedElement = null;
let mutationObserver = null;
// Keyboard overlay state
let keyboardOverlays = [];
const extractProps = (props) => {
return Object.entries(props || {}).reduce((acc, [key, value]) => {
if (['string', 'number', 'boolean'].includes(typeof value) &&
!['data-loc', 'data-component', 'children'].includes(key)) {
acc[key] = value;
}
return acc;
}, {});
};
/**
* Core element selection logic shared between mouse and keyboard selection
* @param makeEditable - Whether to make text elements editable immediately (false during Tab navigation)
*/
function selectElement(element, makeEditable = true) {
// Get React fiber info
const reactPropsKey = Object.keys(element).find((key) => key.startsWith('__reactProps'));
const reactFiberKey = Object.keys(element).find((key) => key.startsWith('__reactFiber'));
const fiberProps = reactPropsKey ? element[reactPropsKey] : undefined;
const fiberNode = reactFiberKey ? element[reactFiberKey] : undefined;
if (!fiberNode) {
return;
}
const elementDynamic = element.getAttribute('data-dynamic');
const isTextElement = typeof fiberProps.children === 'string';
const editable = !elementDynamic && isTextElement;
currentSelectedElement = element;
// Send selection message
const payload = createElementPayload(element);
sendMessageToBridge({
type: 'spark:designer:host:element:selected',
element: payload,
});
// Show selected overlay
document.querySelectorAll('.debugger-overlay').forEach((x) => x.remove());
showOverlay(element);
// Disconnect previous observer if it exists
if (mutationObserver) {
mutationObserver.disconnect();
mutationObserver = null;
}
// Set up mutation observer for the selected element
mutationObserver = new MutationObserver((mutationsList) => {
for (const mutation of mutationsList) {
if (mutation.type === 'attributes') {
sendMessageToBridge({
type: 'spark:designer:bridge:element:updated',
element: createElementPayload(element),
});
updateOverlayPositions();
}
}
});
mutationObserver.observe(element, {
attributes: true,
attributeFilter: ['data-loc', 'data-loc-end', 'data-component-loc', 'data-component-loc-end', 'class'],
});
// Make editable if applicable AND makeEditable is true
// During Tab navigation (makeEditable=false), we don't steal focus with contentEditable
// User can press Enter to explicitly make it editable
if (editable && makeEditable) {
element.contentEditable = 'true';
element.focus();
element.addEventListener('blur', () => {
element.contentEditable = 'false';
sendMessageToBridge({
type: 'spark:designer:bridge:element:updated',
element: createElementPayload(element),
});
}, { once: true });
}
}
function createElementPayload(element) {
const reactPropsKey = Object.keys(element).find((key) => key.startsWith('__reactProps'));
const reactFiberKey = Object.keys(element).find((key) => key.startsWith('__reactFiber'));
const fiberProps = reactPropsKey ? element[reactPropsKey] : undefined;
const fiberNode = reactFiberKey ? element[reactFiberKey] : undefined;
const elementDataLoc = element.getAttribute('data-loc')?.split(':');
const elementDataLocEnd = element.getAttribute('data-loc-end')?.split(':');
const componentLoc = element.getAttribute('data-component-loc')?.split(':');
const componentLocEnd = element.getAttribute('data-component-loc-end')?.split(':');
const elementDynamic = element.getAttribute('data-dynamic');
const isTextElement = typeof fiberProps.children === 'string';
const editable = !elementDynamic && isTextElement;
const rect = element.getBoundingClientRect();
return {
tag: fiberNode.type?.name || fiberNode.type,
component: {
location: componentLoc && componentLocEnd
? {
start: {
filePath: componentLoc?.[0],
line: parseInt(componentLoc?.[1], 10),
column: parseInt(componentLoc?.[2], 10),
},
end: {
filePath: componentLocEnd?.[0],
line: parseInt(componentLocEnd?.[1], 10),
column: parseInt(componentLocEnd?.[2], 10),
},
}
: null,
},
props: extractProps(fiberProps),
location: elementDataLoc && elementDataLocEnd
? {
start: {
filePath: elementDataLoc[0],
line: parseInt(elementDataLoc[1], 10),
column: parseInt(elementDataLoc[2], 10),
},
end: {
filePath: elementDataLocEnd[0],
line: parseInt(elementDataLocEnd[1], 10),
column: parseInt(elementDataLocEnd[2], 10),
},
}
: null,
instanceCount: document.querySelectorAll(`[data-loc="${elementDataLoc}"]`).length,
position: {
top: rect.top,
left: rect.left,
width: rect.width,
height: rect.height,
},
editable,
text: isTextElement ? element.innerText : null,
class: element.getAttribute('class'),
};
}
function handleClick(event) {
const element = event.target;
if (!(element instanceof HTMLElement)) {
return;
}
// Skip our keyboard overlay buttons - let their own handlers deal with selection
// IMPORTANT: Check this BEFORE preventDefault/stopPropagation so button handler can fire
if (element.classList.contains('spark-keyboard-overlay')) {
return;
}
// Only prevent default and stop propagation for actual element clicks
event.preventDefault();
event.stopPropagation();
if (element === currentSelectedElement && element.contentEditable === 'true') {
return;
}
else {
if (currentSelectedElement?.contentEditable === 'true') {
currentSelectedElement.contentEditable = 'false';
currentSelectedElement.blur();
}
}
if (event.target === document.documentElement || element === currentSelectedElement) {
document.querySelectorAll('.debugger-overlay').forEach((x) => x.remove());
if (element === currentSelectedElement) {
currentHighlightedElement = currentSelectedElement;
showOverlay(currentHighlightedElement);
}
currentSelectedElement = null;
sendMessageToBridge({
type: 'spark:designer:bridge:element:deselected',
element: null,
});
return;
}
// Check if element has React fiber before selecting
const reactFiberKey = Object.keys(element).find((key) => key.startsWith('__reactFiber'));
if (!reactFiberKey || !element[reactFiberKey]) {
return;
}
// Use shared selection logic
selectElement(element);
}
function showOverlay(element) {
const elementDataLoc = element.getAttribute('data-loc');
const componentDataLoc = element.getAttribute('data-component-loc');
const computedStyles = window.getComputedStyle(element);
const elements = componentDataLoc
? document.querySelectorAll(`[data-component-loc="${componentDataLoc}"]`)
: document.querySelectorAll(`[data-loc="${elementDataLoc}"]`);
elements.forEach((el) => {
const rect = el.getBoundingClientRect();
const overlay = document.createElement('div');
overlay.style.setProperty('--fg-color', '#4493f8');
overlay.className = 'debugger-overlay';
overlay.style.position = 'fixed';
overlay.style.pointerEvents = 'none';
overlay.style.border = '1px solid var(--fg-color)';
overlay.style.left = rect.left + 'px';
overlay.style.top = rect.top + 'px';
overlay.style.width = rect.width + 'px';
overlay.style.height = rect.height + 'px';
overlay.style.color = 'var(--fg-color)';
overlay.style.borderRadius = parseInt(computedStyles.borderRadius) + 'px';
overlay.style.borderTopLeftRadius = '0px';
overlay.setAttribute('data-element-name', element.tagName.toLowerCase());
overlay.setAttribute('data-overlay-loc', elementDataLoc);
if (el === currentHighlightedElement || el === currentSelectedElement) {
overlay.style.setProperty('--display-tag', 'flex');
}
if (componentDataLoc) {
// overlay.setAttribute('data-element-name', componentName)
overlay.style.setProperty('--fg-color', '#AB7DF8');
}
document.body.appendChild(overlay);
});
}
function updateOverlayPositions() {
document.querySelectorAll('.debugger-overlay').forEach((x) => x.remove());
if (currentSelectedElement && currentSelectedElement !== currentHighlightedElement) {
showOverlay(currentSelectedElement);
}
if (currentHighlightedElement) {
showOverlay(currentHighlightedElement);
}
if (currentSelectedElement) {
sendMessageToBridge({
type: 'spark:designer:bridge:element:updated',
element: createElementPayload(currentSelectedElement),
});
}
}
function handleMouseOver(event) {
const element = event.target;
if (!(element instanceof HTMLElement))
return;
if (element === currentSelectedElement) {
document.querySelectorAll('.debugger-overlay').forEach((x) => x.remove());
showOverlay(currentSelectedElement);
return;
}
if (element !== currentHighlightedElement) {
document.querySelectorAll('.debugger-overlay').forEach((x) => x.remove());
}
currentHighlightedElement = element;
// if the element is not the same as the current selected element, show the overlay
if (currentSelectedElement && currentSelectedElement !== currentHighlightedElement) {
showOverlay(currentSelectedElement);
}
// we want to show the current overlay to be later in the DOM tree
showOverlay(currentHighlightedElement);
}
function handleMouseOut(event) {
if (!event.relatedTarget) {
currentHighlightedElement = null;
document.querySelectorAll('.debugger-overlay').forEach((x) => x.remove());
if (currentSelectedElement) {
showOverlay(currentSelectedElement);
}
}
}
function throttle(func, limit) {
let inThrottle;
return function (...args) {
const context = this;
if (!inThrottle) {
func.apply(context, args);
inThrottle = true;
setTimeout(() => (inThrottle = false), limit);
}
};
}
const updateOverlayPositionsThrottled = throttle(updateOverlayPositions, 10); // ~60fps
/**
* Creates keyboard-accessible overlay buttons for selectable elements
* These overlays enable Tab-based keyboard navigation without modifying user's elements
*/
function createKeyboardOverlays() {
// Remove any existing overlays first
removeKeyboardOverlays();
// Find all selectable elements with data-loc attribute
const elements = document.querySelectorAll('[data-loc]');
const selectableElements = [];
elements.forEach((element) => {
// Skip root elements
if (element.tagName === 'HTML' || element.tagName === 'BODY') {
return;
}
// Skip our own overlay buttons
if (element.classList.contains('spark-keyboard-overlay')) {
return;
}
// Skip hidden or zero-size elements
const rect = element.getBoundingClientRect();
// In test environments (jsdom), getBoundingClientRect may return 0
// So we also check computed styles
const computedStyle = window.getComputedStyle(element);
const hasSize = rect.width > 0 || rect.height > 0 ||
(computedStyle.width !== '0px' && computedStyle.height !== '0px');
if (!hasSize) {
return;
}
// Only include elements with React fiber (valid components)
const reactFiberKey = Object.keys(element).find((key) => key.startsWith('__reactFiber'));
if (reactFiberKey) {
const fiber = element[reactFiberKey];
if (fiber && fiber.stateNode === element) {
element._cachedComponentName = fiber?.type?.name || fiber?.type || element.tagName.toLowerCase();
selectableElements.push(element);
}
}
});
// Create overlay button for each selectable element
selectableElements.forEach((element, index) => {
const rect = element.getBoundingClientRect();
// Skip elements with zero dimensions (they break Tab navigation)
// Even though they passed the initial size filter, getBoundingClientRect can return 0x0
if (rect.width === 0 || rect.height === 0) {
return;
}
// Create focusable button overlay
const button = document.createElement('button');
button.className = 'spark-keyboard-overlay';
button.setAttribute('type', 'button');
button.setAttribute('tabindex', '0');
// Use cached component name from earlier lookup
const componentName = element._cachedComponentName || element.tagName.toLowerCase();
button.setAttribute('aria-label', `Select ${componentName} element, ${index + 1} of ${selectableElements.length}`);
button.setAttribute('data-target-loc', element.getAttribute('data-loc') || '');
// Position button over the element
// Use rect if available, otherwise use element's offset/computed style
const left = rect.left || element.offsetLeft || 0;
const top = rect.top || element.offsetTop || 0;
const width = rect.width || element.offsetWidth || parseFloat(window.getComputedStyle(element).width) || 0;
const height = rect.height || element.offsetHeight || parseFloat(window.getComputedStyle(element).height) || 0;
button.style.position = 'fixed';
button.style.left = left + 'px';
button.style.top = top + 'px';
button.style.width = width + 'px';
button.style.height = height + 'px';
// Make invisible but focusable
button.style.opacity = '0';
button.style.border = 'none';
button.style.background = 'transparent';
button.style.cursor = 'pointer';
button.style.zIndex = '9998';
button.style.padding = '0';
button.style.margin = '0';
// Show visual feedback on focus
button.addEventListener('focus', (e) => {
currentHighlightedElement = element;
document.querySelectorAll('.debugger-overlay').forEach((x) => x.remove());
// Restore selected element overlay if different from focused element
if (currentSelectedElement && currentSelectedElement !== element) {
showOverlay(currentSelectedElement);
}
// Show hover overlay for focused element (same as mouse hover)
showOverlay(element);
// Auto-select the focused element so modal/input updates
selectElement(element, false);
});
// Remove hover overlay when Tab moves away
button.addEventListener('blur', (e) => {
if (currentHighlightedElement === element) {
currentHighlightedElement = null;
document.querySelectorAll('.debugger-overlay').forEach((x) => x.remove());
// Restore selected element overlay if exists AND it's different from the blurred element
if (currentSelectedElement && currentSelectedElement !== element) {
showOverlay(currentSelectedElement);
}
}
});
// Handle keyboard events on overlay buttons
button.addEventListener('keydown', (e) => {
// Escape = exit selector mode (tell parent to disable)
if (e.key === 'Escape') {
e.preventDefault();
e.stopPropagation();
// Tell parent window to disable selector mode
sendMessageToBridge({
type: 'spark:designer:host:disable-requested',
});
return; // Don't process other handlers
}
// Shift+Enter starts the cycle: element → input → theme panel → element
if (e.key === 'Enter' && e.shiftKey) {
// Prevent default Shift+Enter behavior
e.preventDefault();
e.stopPropagation();
// Tell parent window to focus its input field (first step in cycle)
sendMessageToBridge({
type: 'spark:designer:host:focus-input-requested',
buttonDataLoc: button.getAttribute('data-target-loc'),
});
return; // Don't process other handlers
}
});
// Handle click/Enter to select element
button.addEventListener('click', (e) => {
e.preventDefault();
e.stopPropagation();
// Select element immediately (makeEditable=true for click, unlike Tab navigation)
selectElement(element, true);
});
document.body.appendChild(button);
keyboardOverlays.push(button);
});
// Auto-focus the first overlay button for keyboard-only users
// This allows them to start Tab navigation immediately after enabling selector mode
if (keyboardOverlays.length > 0) {
// Use setTimeout to ensure the button is fully rendered and focusable
setTimeout(() => {
// Check again in case overlays were removed before timeout fires
if (keyboardOverlays.length > 0 && keyboardOverlays[0]) {
keyboardOverlays[0].focus();
}
}, 0);
}
}
/**
* Removes all keyboard overlay buttons
*/
function removeKeyboardOverlays() {
keyboardOverlays.forEach((button) => {
button.remove();
});
keyboardOverlays = [];
}
/**
* Updates positions of keyboard overlay buttons (for scroll/resize)
*/
function updateKeyboardOverlayPositions() {
keyboardOverlays.forEach((button) => {
const targetLoc = button.getAttribute('data-target-loc');
if (!targetLoc)
return;
const element = document.querySelector(`[data-loc="${CSS.escape(targetLoc)}"]`);
if (!element)
return;
const rect = element.getBoundingClientRect();
button.style.left = rect.left + 'px';
button.style.top = rect.top + 'px';
button.style.width = rect.width + 'px';
button.style.height = rect.height + 'px';
});
}
const updateKeyboardOverlayPositionsThrottled = throttle(updateKeyboardOverlayPositions, 10); // ~100fps max (10ms throttle)
/**
* Prevents default behavior on native interactive elements
* This allows them to be selected like any other element while preventing their normal actions
*/
function handleNativeElementInteraction(event) {
const element = event.target;
// Prevent default button/link/input behavior
event.preventDefault();
event.stopPropagation(); // For Enter key on native elements, select them like we do with overlay buttons
if (event.type === 'keydown' && event.key === 'Enter') {
selectElement(element);
}
}
/**
* Adds event listeners to native interactive elements to override their default behavior
*/
function disableNativeInteractivity() {
const nativeElements = document.querySelectorAll('button, input, textarea, select, a[href]');
nativeElements.forEach((element) => {
// Prevent default click behavior (but allow selection via handleClick)
element.addEventListener('click', handleNativeElementInteraction, true);
// Prevent Enter key from triggering button action, use it for selection instead
element.addEventListener('keydown', handleNativeElementInteraction, true);
// Mark element as having listeners for cleanup
element.setAttribute('data-spark-intercepted', 'true');
});
}
/**
* Removes event listeners from native interactive elements
*/
function restoreNativeInteractivity() {
const nativeElements = document.querySelectorAll('[data-spark-intercepted="true"]');
nativeElements.forEach((element) => {
element.removeEventListener('click', handleNativeElementInteraction, true);
element.removeEventListener('keydown', handleNativeElementInteraction, true);
element.removeAttribute('data-spark-intercepted');
});
}
/**
* Handle messages from the parent window
*/
function handleMessage(message) {
switch (message.type) {
case 'spark:designer:bridge:enable': { // IMPORTANT: Disable native interactivity FIRST so our handlers fire before handleClick
// This prevents native button/link behavior while allowing element selection
disableNativeInteractivity();
window.addEventListener('click', handleClick, true);
window.addEventListener('mouseover', handleMouseOver, true);
window.addEventListener('scroll', updateOverlayPositionsThrottled, {
passive: true,
});
window.addEventListener('resize', updateOverlayPositionsThrottled, {
passive: true,
});
// when cursor leaves the window
document.addEventListener('mouseout', handleMouseOut, true);
// Create keyboard-accessible overlays
createKeyboardOverlays();
// Update keyboard overlay positions on scroll/resize
window.addEventListener('scroll', updateKeyboardOverlayPositionsThrottled, {
passive: true,
});
window.addEventListener('resize', updateKeyboardOverlayPositionsThrottled, {
passive: true,
});
break;
}
case 'spark:designer:bridge:disable': {
currentHighlightedElement = null;
currentSelectedElement = null;
window.removeEventListener('click', handleClick, true);
window.removeEventListener('mouseover', handleMouseOver, true);
window.removeEventListener('scroll', updateOverlayPositionsThrottled);
window.removeEventListener('resize', updateOverlayPositionsThrottled);
document.removeEventListener('mouseout', handleMouseOut, true);
document.querySelectorAll('.debugger-overlay').forEach((x) => x.remove());
if (mutationObserver) {
mutationObserver.disconnect();
mutationObserver = null;
}
// Clean up keyboard overlays
removeKeyboardOverlays();
// Remove keyboard-specific scroll/resize listeners (separate from mouse overlay listeners above)
window.removeEventListener('scroll', updateKeyboardOverlayPositionsThrottled);
window.removeEventListener('resize', updateKeyboardOverlayPositionsThrottled);
// Restore native interactivity
restoreNativeInteractivity();
break;
}
case 'spark:designer:bridge:deselect': {
document.querySelectorAll('.debugger-overlay').forEach((x) => x.remove());
currentSelectedElement = null;
if (mutationObserver) {
mutationObserver.disconnect();
mutationObserver = null;
}
break;
}
case 'spark:designer:bridge:restore-focus': {
const { buttonDataLoc } = message;
// Find the overlay button at the specified data-loc and focus it
if (buttonDataLoc) {
const button = document.querySelector(`.spark-keyboard-overlay[data-target-loc="${CSS.escape(buttonDataLoc)}"]`);
if (button) {
button.focus();
}
}
break;
}
case 'spark:designer:bridge:restore-focus-from-theme-panel': {
const { buttonDataLoc } = message;
// Find the overlay button at the specified data-loc and focus it
// Same as restore-focus, but specifically from theme panel navigation
if (buttonDataLoc) {
const button = document.querySelector(`.spark-keyboard-overlay[data-target-loc="${CSS.escape(buttonDataLoc)}"]`);
if (button) {
button.focus();
}
}
break;
}
case 'spark:designer:bridge:update-theme-token': {
const { token, value } = message;
document.documentElement.style.setProperty(`--${token}`, value);
break;
}
case 'spark:designer:bridge:update-element-token': {
const { location, name, value } = message;
const { filePath, line, column } = location;
document.querySelectorAll(`[data-loc="${filePath}:${line}:${column}"]`).forEach((el) => {
el.style.setProperty(name, value);
});
break;
}
case 'spark:designer:bridge:update-class-name': {
const { location, className, replace } = message;
const { filePath, line, column } = location;
document.querySelectorAll(`[data-loc="${filePath}:${line}:${column}"]`).forEach((el) => {
const elementClassName = el.getAttribute('class') || '';
// Simple concatenation - if more sophisticated merging is needed, consider adding tailwind-merge
const newClassName = replace ? className : `${elementClassName} ${className}`.trim();
el.setAttribute('class', newClassName);
});
break;
}
}
}
/**
* Listen for messages from the parent window
*/
window.addEventListener('message', (event) => {
handleMessage(event.data);
});
//# sourceMappingURL=designerHost.js.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,8 @@
import type { PluginOption } from 'vite';
export default function getCwd(): string;
export declare function tagFile(source: string, filePath: string): {
code: string;
map: any;
};
export declare const tagSourcePlugin: () => PluginOption;
export declare const designerHost: () => PluginOption;

35709
packages/spark-tools/dist/designerPlugin.js vendored Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,12 @@
const EventType = {
SPARK_RUNTIME_ERROR: 'sparkRuntimeError',
SPARK_RUNTIME_PING: 'sparkRuntimePing',
SPARK_RUNTIME_LOADED: 'sparkRuntimeLoaded',
SPARK_VITE_WS_CONNECT: 'sparkViteWsConnect',
SPARK_VITE_WS_DISCONNECT: 'sparkViteWsDisconnect',
SPARK_VITE_ERROR: 'sparkViteError',
SPARK_VITE_AFTER_UPDATE: 'sparkViteAfterUpdate',
ROOT_ELEMENT_STATUS: 'rootElementStatus'};
export { EventType as E };
//# sourceMappingURL=heartbeat-event-types-BmKuwNhb.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"heartbeat-event-types-BmKuwNhb.js","sources":["../src/types/heartbeat-event-types.ts"],"sourcesContent":[null],"names":[],"mappings":"AAAO,MAAM,SAAS,GAAG;AACvB,IAAA,mBAAmB,EAAE,mBAAmB;AACxC,IAAA,kBAAkB,EAAE,kBAAkB;AACtC,IAAA,oBAAoB,EAAE,oBAAoB;AAC1C,IAAA,qBAAqB,EAAE,oBAAoB;AAC3C,IAAA,wBAAwB,EAAE,uBAAuB;AACjD,IAAA,gBAAgB,EAAE,gBAAgB;AAClC,IAAA,uBAAuB,EAAE,sBAAsB;AAC/C,IAAA,mBAAmB,EAAE;;;;"}

157
packages/spark-tools/dist/heartbeat.js vendored Normal file
View File

@@ -0,0 +1,157 @@
import { E as EventType } from './heartbeat-event-types-BmKuwNhb.js';
const VERSION = "ec61fa57186e5a2ceb3003a660f42fd762e82193";
const WORKBENCH_ORIGIN = import.meta.env.VITE_WORKBENCH_ORIGIN || "https://github.com";
async function getSourceMapConsumer(sourceMap) {
if (window.sourceMap !== undefined) {
return await new window.sourceMap.SourceMapConsumer(sourceMap);
}
// @ts-ignore
await import('https://unpkg.com/source-map@0.7.3/dist/source-map.js');
window.sourceMap.SourceMapConsumer.initialize({
"lib/mappings.wasm": "https://unpkg.com/source-map@0.7.3/lib/mappings.wasm",
});
return await new window.sourceMap.SourceMapConsumer(sourceMap);
}
async function wait(ms) {
return new Promise((resolve) => setTimeout(resolve, ms));
}
/**
* Check whether the root element of the app exists.
*/
function getRootElement() {
return document.getElementById("root");
}
/**
* Checks if the given element is null or empty.
*/
function isEmptyElement(element) {
if (element === null) {
return true; // Treat missing element as empty
}
return element.textContent?.trim() === "";
}
async function monitorRootElement() {
await wait(200); // Wait a bit for the root element to be rendered
console.info("Root element monitoring enabled");
let checkInterval = 500; // Start with 500 milliseconds
const checkRootElement = () => {
const rootElement = getRootElement();
window.parent.postMessage({
type: EventType.ROOT_ELEMENT_STATUS,
payload: {
timestamp: Date.now(),
isEmpty: isEmptyElement(rootElement),
exists: !!rootElement,
},
}, WORKBENCH_ORIGIN);
clearInterval(intervalId);
checkInterval = 3000;
intervalId = setInterval(checkRootElement, checkInterval);
};
let intervalId = setInterval(checkRootElement, checkInterval);
checkRootElement();
}
// Handle JavaScript errors
function setupErrorListener() {
console.info("Runtime heartbeat enabled");
window.addEventListener("error", (event) => {
const { message, filename, lineno, colno } = event;
fetch(filename)
.then(async (response) => {
if (response.ok) {
const rawFile = await response.text();
const base64SourceMap = rawFile.split("# sourceMappingURL=").pop();
const rawBase64SourceMap = base64SourceMap.split("data:application/json;base64,").pop();
const sourceMap = JSON.parse(atob(rawBase64SourceMap));
const consumer = await getSourceMapConsumer(sourceMap);
const originalPosition = consumer.originalPositionFor({
line: lineno,
column: colno,
});
const payload = {
line: originalPosition.line,
column: originalPosition.column,
path: new URL(filename).pathname,
message,
};
window.parent.postMessage({
type: EventType.SPARK_RUNTIME_ERROR,
payload,
}, WORKBENCH_ORIGIN);
}
})
.catch(() => {
const payload = {
line: lineno,
column: colno,
path: new URL(filename).pathname,
message,
sourceMap: false,
};
window.parent.postMessage({
type: EventType.SPARK_RUNTIME_ERROR,
payload,
}, WORKBENCH_ORIGIN);
});
});
}
function initializeViteHeartbeat() {
const viteServerSessionId = import.meta.env.VITE_SERVER_SESSION_ID || "unset";
console.info("Vite heartbeat enabled. Server session ID:", viteServerSessionId);
import.meta.hot?.on("vite:ws:connect", () => {
console.info("Vite server WebSocket connected");
window.parent.postMessage({
type: EventType.SPARK_VITE_WS_CONNECT,
payload: { timestamp: Date.now(), viteServerSessionId },
}, WORKBENCH_ORIGIN);
});
import.meta.hot?.on("vite:ws:disconnect", () => {
console.info("Vite server WebSocket disconnected");
window.parent.postMessage({
type: EventType.SPARK_VITE_WS_DISCONNECT,
payload: { timestamp: Date.now(), viteServerSessionId },
}, WORKBENCH_ORIGIN);
});
import.meta.hot?.on("vite:error", (error) => {
console.warn("Vite server error:", error);
window.parent.postMessage({
type: EventType.SPARK_VITE_ERROR,
payload: { error, timestamp: Date.now(), viteServerSessionId },
}, WORKBENCH_ORIGIN);
});
import.meta.hot?.on("vite:afterUpdate", (updateInfo) => {
window.parent.postMessage({
type: EventType.SPARK_VITE_AFTER_UPDATE,
payload: { updateInfo, timestamp: Date.now(), viteServerSessionId },
}, WORKBENCH_ORIGIN);
if (isEmptyElement(getRootElement())) {
wait(100).then(() => {
window.location.reload();
});
}
});
}
function heartbeat() {
console.info(`Spark Tools version: ${VERSION}`);
setupErrorListener();
monitorRootElement();
// Tell parent the runtime is ready.
window.parent.postMessage({
type: EventType.SPARK_RUNTIME_PING,
payload: {
version: VERSION,
timestamp: Date.now(),
},
}, WORKBENCH_ORIGIN);
}
heartbeat();
if (import.meta.hot) {
initializeViteHeartbeat();
}
else {
console.error(`Vite HMR is not available`);
}
export { setupErrorListener };
//# sourceMappingURL=heartbeat.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"heartbeat.js","sources":["../src/heartbeat/heartbeat.ts"],"sourcesContent":[null],"names":[],"mappings":"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"}

View File

@@ -0,0 +1,13 @@
declare global {
interface Window {
sourceMap?: {
SourceMapConsumer: {
new (sourceMap: any): any;
initialize(config: {
[key: string]: string;
}): void;
};
};
}
}
export declare function setupErrorListener(): void;

View File

@@ -0,0 +1,3 @@
import type { PluginOption } from 'vite';
export declare const heartbeatPlugin: () => PluginOption;
export declare const runtimeTelemetryPlugin: () => PluginOption;

View File

@@ -0,0 +1,30 @@
import { createRequire } from 'node:module';
const require$1 = createRequire(import.meta.url);
const src = require$1.resolve('./heartbeat');
const heartbeatPlugin = () => {
return {
name: 'heartbeat',
apply: 'serve', // Only apply this plugin for the dev server only
transformIndexHtml(html) {
return {
html,
tags: [
{
tag: 'script',
attrs: {
type: 'module',
src: src,
},
injectTo: 'head',
},
],
};
},
};
};
// Backward compatibility alias
const runtimeTelemetryPlugin = heartbeatPlugin;
export { heartbeatPlugin, runtimeTelemetryPlugin };
//# sourceMappingURL=heartbeatPlugin.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"heartbeatPlugin.js","sources":["../src/heartbeat/heartbeatPlugin.ts"],"sourcesContent":[null],"names":["require"],"mappings":";;AAGA,MAAMA,SAAO,GAAG,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC;AAC9C,MAAM,GAAG,GAAGA,SAAO,CAAC,OAAO,CAAC,aAAa,CAAC;AAEnC,MAAM,eAAe,GAAG,MAAmB;IAChD,OAAO;AACL,QAAA,IAAI,EAAE,WAAW;QACjB,KAAK,EAAE,OAAO;AACd,QAAA,kBAAkB,CAAC,IAAI,EAAA;YACrB,OAAO;gBACL,IAAI;AACJ,gBAAA,IAAI,EAAE;AACJ,oBAAA;AACE,wBAAA,GAAG,EAAE,QAAQ;AACb,wBAAA,KAAK,EAAE;AACL,4BAAA,IAAI,EAAE,QAAQ;AACd,4BAAA,GAAG,EAAE,GAAG;AACT,yBAAA;AACD,wBAAA,QAAQ,EAAE,MAAM;AACjB,qBAAA;AACF,iBAAA;aACF;QACH,CAAC;KACF;AACH;AAEA;AACO,MAAM,sBAAsB,GAAG;;;;"}

View File

@@ -0,0 +1 @@
export * from './use-kv';

View File

@@ -0,0 +1,21 @@
/**
* A hook that works similarly to React.useState, but persists the value using the Spark Runtime.
* The value is automatically retrieved from the Spark Runtime on mount and updated on state change.
* While the initial value is being fetched, the `initialValue` is being used.
* Use this component when you need to persist/store/remember values. Note that the current value
* may be undefined if no value has been set yet or if the value has been deleted.
*
* @param key - The key under which to store the value.
* @param initialValue - The initial value to use if no stored value is found.
* @returns An array containing the current value, a setter function, and a delete function.
*
* @example
* import { useKV } from "@github/spark/hooks";
*
* const [count, setCount, deleteCount] = useKV("count", 0);
* @example
* import { useKV } from "@github/spark/hooks";
*
* const [name, setName] = useKV("name", "");
*/
export declare function useKV<T = string>(key: string, initialValue?: NoInfer<T>): readonly [T | undefined, (newValue: T | ((oldValue?: T) => T)) => void, () => void];

View File

@@ -0,0 +1 @@
export declare function useTheme(): Theme;

79
packages/spark-tools/dist/index.js vendored Normal file
View File

@@ -0,0 +1,79 @@
import { useState, useMemo, useCallback, useEffect } from 'react';
import { K as KVClient, a as KvEventType } from './kv-DBiZoNWq.js';
/**
* A hook that works similarly to React.useState, but persists the value using the Spark Runtime.
* The value is automatically retrieved from the Spark Runtime on mount and updated on state change.
* While the initial value is being fetched, the `initialValue` is being used.
* Use this component when you need to persist/store/remember values. Note that the current value
* may be undefined if no value has been set yet or if the value has been deleted.
*
* @param key - The key under which to store the value.
* @param initialValue - The initial value to use if no stored value is found.
* @returns An array containing the current value, a setter function, and a delete function.
*
* @example
* import { useKV } from "@github/spark/hooks";
*
* const [count, setCount, deleteCount] = useKV("count", 0);
* @example
* import { useKV } from "@github/spark/hooks";
*
* const [name, setName] = useKV("name", "");
*/
function useKV(key, initialValue) {
const [value, setValue] = useState(initialValue);
const kvClient = useMemo(() => {
return new KVClient();
}, []);
const handleMessage = useCallback(async (event) => {
switch (event.data.type) {
case KvEventType.SPARK_KV_DELETED:
// When we're notified from Workbench that a key has been deleted,
// let's erase our understanding of the value.
if (event.data.payload.key === key) {
setValue(undefined);
}
return;
case KvEventType.SPARK_KV_UPDATED:
// When we're notified from Workbench that a key has been updated,
// let's go grab the new value from the database directly.
if (event.data.payload.key === key) {
setValue(await kvClient.getKey(key));
}
return;
}
}, [key]);
useEffect(() => {
if (!import.meta.env.DEV) {
return;
}
window.addEventListener('message', handleMessage);
return () => {
window.removeEventListener('message', handleMessage);
};
}, [key, handleMessage]);
useEffect(() => {
async function getOrSetKey() {
setValue(await kvClient.getOrSetKey(key, initialValue));
}
getOrSetKey();
}, [kvClient]);
const deleteValue = useCallback(() => {
kvClient.deleteKey(key);
setValue(undefined);
}, [key]);
const userSetValue = useCallback((newValue) => {
setValue((currentValue) => {
const nextValue = typeof newValue === 'function'
? newValue(currentValue)
: newValue;
kvClient.setKey(key, nextValue);
return nextValue;
});
}, [key, kvClient]);
return [value, userSetValue, deleteValue];
}
export { useKV };
//# sourceMappingURL=index.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"index.js","sources":["../src/hooks/use-kv.ts"],"sourcesContent":[null],"names":[],"mappings":";;;AAKA;;;;;;;;;;;;;;;;;;;AAmBG;AACG,SAAU,KAAK,CAAa,GAAW,EAAE,YAAyB,EAAA;IACtE,MAAM,CAAC,KAAK,EAAE,QAAQ,CAAC,GAAG,QAAQ,CAAgB,YAAY,CAAC;AAC/D,IAAA,MAAM,QAAQ,GAAG,OAAO,CAAC,MAAK;QAC5B,OAAO,IAAI,QAAQ,EAAE;IACvB,CAAC,EAAE,EAAE,CAAC;IAEN,MAAM,aAAa,GAAG,WAAW,CAC/B,OAAO,KAAmB,KAAI;AAC5B,QAAA,QAAQ,KAAK,CAAC,IAAI,CAAC,IAAI;YACrB,KAAK,WAAW,CAAC,gBAAgB;;;gBAG/B,IAAI,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,KAAK,GAAG,EAAE;oBAClC,QAAQ,CAAC,SAAS,CAAC;gBACrB;gBACA;YACF,KAAK,WAAW,CAAC,gBAAgB;;;gBAG/B,IAAI,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,KAAK,GAAG,EAAE;oBAClC,QAAQ,CAAC,MAAM,QAAQ,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;gBACtC;gBACA;;AAEN,IAAA,CAAC,EAAE,CAAC,GAAG,CAAC,CAAC;IAEX,SAAS,CAAC,MAAK;QACb,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE;YACxB;QACF;AAEA,QAAA,MAAM,CAAC,gBAAgB,CAAC,SAAS,EAAE,aAAa,CAAC;AACjD,QAAA,OAAO,MAAK;AACV,YAAA,MAAM,CAAC,mBAAmB,CAAC,SAAS,EAAE,aAAa,CAAC;AACtD,QAAA,CAAC;AACH,IAAA,CAAC,EAAE,CAAC,GAAG,EAAE,aAAa,CAAC,CAAC;IAExB,SAAS,CAAC,MAAK;AACb,QAAA,eAAe,WAAW,GAAA;YACxB,QAAQ,CACN,MAAM,QAAQ,CAAC,WAAW,CAAC,GAAG,EAAE,YAAY,CAAC,CAC9C;QACH;AACA,QAAA,WAAW,EAAE;AACf,IAAA,CAAC,EAAE,CAAC,QAAQ,CAAC,CAAC;AAEd,IAAA,MAAM,WAAW,GAAG,WAAW,CAAC,MAAK;AACnC,QAAA,QAAQ,CAAC,SAAS,CAAC,GAAG,CAAC;QACvB,QAAQ,CAAC,SAAS,CAAC;AACrB,IAAA,CAAC,EAAE,CAAC,GAAG,CAAC,CAAC;AAET,IAAA,MAAM,YAAY,GAAG,WAAW,CAC9B,CAAC,QAAmC,KAAI;AACtC,QAAA,QAAQ,CAAC,CAAC,YAAY,KAAI;AACxB,YAAA,MAAM,SAAS,GAAG,OAAO,QAAQ,KAAK;AACpC,kBAAG,QAAgC,CAAC,YAAY;kBAC9C,QAAQ;AAEZ,YAAA,QAAQ,CAAC,MAAM,CAAC,GAAG,EAAE,SAAS,CAAC;AAC/B,YAAA,OAAO,SAAS;AAClB,QAAA,CAAC,CAAC;AACJ,IAAA,CAAC,EACD,CAAC,GAAG,EAAE,QAAQ,CAAC,CAChB;AAED,IAAA,OAAO,CAAC,KAAK,EAAE,YAAY,EAAE,WAAW,CAAU;AACpD;;;;"}

177
packages/spark-tools/dist/kv-DBiZoNWq.js vendored Normal file
View File

@@ -0,0 +1,177 @@
const KvEventType = {
SPARK_KV_UPDATED: 'sparkKvUpdated',
SPARK_KV_DELETED: 'sparkKvDeleted',
};
// This function allows us to send messages from the Spark back to the Workbench application.
// Specifically, we want to send updates about KV operations, to allow the Workbench
// to update its UI accordingly.
const sendEventToWorkbench = (message) => {
if (import.meta.env.DEV) {
window.parent.postMessage(message, '*');
}
};
class KVClient {
/**
* Retrieves a list of all keys in the KV store.
* @returns A list of all keys in the KV store, or an empty array if there are no keys.
*/
async getKeys() {
// Fetching the root URL will return all keys in the KV store.
const response = await fetch(BASE_KV_SERVICE_URL, {
method: 'GET',
});
if (!response.ok) {
const errorMessage = `Failed to fetch KV keys: ${response.statusText}`;
return Promise.reject(new Error(errorMessage));
}
let json;
try {
json = await response.json();
}
catch (error) {
const errorMessage = 'Failed to parse KV keys response';
return Promise.reject(new Error(errorMessage));
}
if (!Array.isArray(json)) {
const errorMessage = 'KV keys response is not an array';
return Promise.reject(new Error(errorMessage));
}
return json;
}
/**
* Retrieves all key-value pairs from the KV store.
* @returns An object containing all key-value pairs, or an empty object if there are no keys.
*
* TODO: replace with batch request
*/
async getAll() {
const keys = await this.getKeys();
const result = {};
// Fetch all values concurrently
const values = await Promise.all(keys.map(key => this.getKey(key)));
// Build the result object
keys.forEach((key, index) => {
const value = values[index];
if (value !== undefined) {
result[key] = value;
}
});
return result;
}
/**
* Retrieves the value associated with the given key from the KV store.
* @param key The key to retrieve.
* @param collectionName Optional collection name to include as a URL parameter.
* @returns The value associated with the key, or undefined if not found.
*/
async getKey(key, collectionName) {
let url = `${BASE_KV_SERVICE_URL}/${encodeURIComponent(key)}`;
if (collectionName) {
url += `?collection=${encodeURIComponent(collectionName)}`;
}
const response = await fetch(url, {
method: 'GET',
headers: {
'Content-Type': `text/plain`,
},
});
if (!response.ok) {
const errorMessage = `Failed to fetch KV key: ${response.statusText}`;
if (response.status === 404) {
// If the key does not exist, return undefined
return undefined;
}
// For other errors, reject with an error message
return Promise.reject(new Error(errorMessage));
}
const responseText = await response.text();
// Extract the value from the response text.
// Important to remember that even a simple string should be returned to us as a JSON-encoded value,
// meaning that the parse should succeed.
try {
return JSON.parse(responseText);
}
catch (error) {
const errorMessage = `Failed to parse KV key response`;
return Promise.reject(new Error(errorMessage));
}
}
/**
* Retrieves the value associated with the given key from the KV store, while also setting it if it does not exist.
* @param key The key to retrieve.
* @param value The value to set if the key does not exist.
* @returns The value associated with the key, whether it was retrieved or newly set.
*/
async getOrSetKey(key, value) {
const existingValue = await this.getKey(key);
if (existingValue !== undefined) {
return existingValue;
}
const response = await fetch(`${BASE_KV_SERVICE_URL}/${encodeURIComponent(key)}`, {
method: 'POST',
headers: {
'Content-Type': `text/plain`,
'X-Spark-Initial': 'true',
},
body: JSON.stringify(value),
});
if (!response.ok) {
const errorMessage = `Failed to set default value for key: ${response.statusText}`;
return Promise.reject(new Error(errorMessage));
}
sendEventToWorkbench({
type: KvEventType.SPARK_KV_UPDATED,
payload: { key },
});
return value;
}
/**
* Sets the value for the given key in the KV store.
* @param key The key to set.
* @param value The value to associate with the key.
* @param collectionName Optional collection name to include as a URL parameter.
* @returns A promise that resolves when the operation is complete.
*/
async setKey(key, value, collectionName) {
let url = `${BASE_KV_SERVICE_URL}/${encodeURIComponent(key)}`;
if (collectionName) {
url += `?collection=${encodeURIComponent(collectionName)}`;
}
const response = await fetch(url, {
method: 'POST',
headers: {
'Content-Type': `text/plain`,
'X-Spark-Initial': 'false',
},
body: JSON.stringify(value),
});
if (!response.ok) {
const errorMessage = `Failed to set key: ${response.statusText}`;
return Promise.reject(new Error(errorMessage));
}
sendEventToWorkbench({
type: KvEventType.SPARK_KV_UPDATED,
payload: { key, value: JSON.stringify(value) },
});
}
/**
* Deletes the value associated with the given key from the KV store.
* @param key The key to delete from the KV store.
* @param collectionName Optional collection name to include as a URL parameter.
*/
async deleteKey(key, collectionName) {
let url = `${BASE_KV_SERVICE_URL}/${encodeURIComponent(key)}`;
if (collectionName) {
url += `?collection=${encodeURIComponent(collectionName)}`;
}
await fetch(url, { method: 'DELETE' });
sendEventToWorkbench({
type: KvEventType.SPARK_KV_DELETED,
payload: { key },
});
}
}
export { KVClient as K, KvEventType as a };
//# sourceMappingURL=kv-DBiZoNWq.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"kv-DBiZoNWq.js","sources":["../src/types/kv-event-types.ts","../src/lib/kv.ts"],"sourcesContent":[null,null],"names":[],"mappings":"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"}

3
packages/spark-tools/dist/lib/db.d.ts vendored Normal file
View File

@@ -0,0 +1,3 @@
export { collection } from './db/collection';
export { DBClient as DB } from './db/db';
export type { Collection, QueryOptions } from './db/collection';

View File

@@ -0,0 +1,74 @@
import { z } from 'zod';
import { Document } from './db';
/**
* Collection interface that provides document operations with schema validation
*/
export interface Collection<T extends z.ZodType> {
/**
* Insert a new document into the collection
* @param data The document data to insert
* @returns Promise that resolves to the inserted document with _id
*/
insert(data: z.infer<T>): Promise<Document<z.infer<T>>>;
/**
* Get a document by its ID
* @param id The document ID
* @returns Promise that resolves to the document or null if not found
*/
get(id: string): Promise<Document<z.infer<T>> | null>;
/**
* Update a document with partial data
* @param id The document ID
* @param data Partial data to update
* @returns Promise that resolves to the updated document or null if not found
*/
update(id: string, data: Partial<z.infer<T>>): Promise<Document<z.infer<T>> | null>;
/**
* Delete a document by its ID
* @param id The document ID
* @returns Promise that resolves to true if deleted, false if not found
*/
delete(id: string): Promise<boolean>;
/**
* Get all documents in the collection
* @returns Promise that resolves to an array of all documents
*/
getAll(): Promise<Document<z.infer<T>>[]>;
/**
* Query documents with filtering, sorting, and limiting
* @param options Query options including where conditions, sorting, and limit
* @returns Promise that resolves to an array of matching documents
*/
query(options?: QueryOptions<T>): Promise<Document<z.infer<T>>[]>;
}
/**
* Query options for filtering, sorting, and limiting results
*/
export interface QueryOptions<T extends z.ZodType> {
/**
* Filter condition
*/
where?: {
field: keyof z.infer<T>;
operator: '==' | '!=' | '>' | '<' | '>=' | '<=';
value: any;
};
/**
* Sort configuration
*/
sortBy?: {
field: keyof z.infer<T>;
direction: 'asc' | 'desc';
};
/**
* Maximum number of results to return
*/
limit?: number;
}
/**
* Create a collection instance with schema validation and clean API
* @param schema The Zod schema for the collection
* @param collectionName The name of the collection
* @returns Collection instance with CRUD operations
*/
export declare function collection<T extends z.ZodType>(schema: T, collectionName: string): Collection<T>;

View File

@@ -0,0 +1,64 @@
import { z } from 'zod';
import { KVClient } from '../kv';
/**
* Document type that combines a schema type with an _id field
*/
export type Document<T> = T & {
_id: string;
};
/**
* DBClient provides methods to interact with Spark's document database.
*/
export declare class DBClient {
private kv;
constructor(kvClient?: KVClient);
/**
* Generate a unique document ID using ULID
* @returns A unique document ID
*/
generateDocId(): string;
/**
* Get all documents in a collection using the DB API
* @param collectionName The name of the collection
* @returns Array of all documents in the collection with id field
*/
getAll<T>(collectionName: string): Promise<Document<T>[]>;
/**
* Insert a document into a collection with schema validation
* @param collectionName The name of the collection
* @param schema The Zod schema for validation
* @param data The document data to insert
* @returns The inserted document with generated id
*/
insert<T extends z.ZodType>(collectionName: string, schema: T, data: z.infer<T>): Promise<Document<z.infer<T>>>;
/**
* Get a document by ID from a collection
* @param collectionName The name of the collection
* @param id The document ID
* @returns The document with id field or null if not found
*/
get<T>(collectionName: string, id: string): Promise<Document<T> | null>;
/**
* Update a document with partial data and schema validation
* @param collectionName The name of the collection
* @param id The document ID
* @param schema The Zod schema for validation
* @param data Partial data to update
* @returns The updated document or null if not found
*/
update<T extends z.ZodType>(collectionName: string, id: string, schema: T, data: Partial<z.infer<T>>): Promise<Document<z.infer<T>> | null>;
/**
* Delete a document from a collection
* @param collectionName The name of the collection
* @param id The document ID to delete
* @returns true if document was deleted, false if not found
*/
delete(collectionName: string, id: string): Promise<boolean>;
/**
* Query documents with filtering
* @param collectionName The name of the collection
* @param filterFn Function to filter documents
* @returns Array of filtered documents
*/
query<T>(collectionName: string, filterFn: (doc: Document<T>) => boolean): Promise<Document<T>[]>;
}

View File

@@ -0,0 +1,6 @@
export * from './db';
export * from './llm';
export * from './octokit';
export * from './spark';
export * from './user';
export * from './utils';

42
packages/spark-tools/dist/lib/kv.d.ts vendored Normal file
View File

@@ -0,0 +1,42 @@
export declare class KVClient {
/**
* Retrieves a list of all keys in the KV store.
* @returns A list of all keys in the KV store, or an empty array if there are no keys.
*/
getKeys(): Promise<string[]>;
/**
* Retrieves all key-value pairs from the KV store.
* @returns An object containing all key-value pairs, or an empty object if there are no keys.
*
* TODO: replace with batch request
*/
getAll(): Promise<Record<string, any>>;
/**
* Retrieves the value associated with the given key from the KV store.
* @param key The key to retrieve.
* @param collectionName Optional collection name to include as a URL parameter.
* @returns The value associated with the key, or undefined if not found.
*/
getKey<T>(key: string, collectionName?: string): Promise<T | undefined>;
/**
* Retrieves the value associated with the given key from the KV store, while also setting it if it does not exist.
* @param key The key to retrieve.
* @param value The value to set if the key does not exist.
* @returns The value associated with the key, whether it was retrieved or newly set.
*/
getOrSetKey<T>(key: string, value: T): Promise<T | undefined>;
/**
* Sets the value for the given key in the KV store.
* @param key The key to set.
* @param value The value to associate with the key.
* @param collectionName Optional collection name to include as a URL parameter.
* @returns A promise that resolves when the operation is complete.
*/
setKey<T>(key: string, value: T, collectionName?: string): Promise<void>;
/**
* Deletes the value associated with the given key from the KV store.
* @param key The key to delete from the KV store.
* @param collectionName Optional collection name to include as a URL parameter.
*/
deleteKey(key: string, collectionName?: string): Promise<void>;
}

View File

@@ -0,0 +1,2 @@
export declare function llm(prompt: string, modelName?: string, jsonMode?: boolean): Promise<string>;
export declare function llmPrompt(strings: string[], ...values: any[]): string;

View File

@@ -0,0 +1,8 @@
declare const octokit: import("@octokit/core").Octokit & {
paginate: import("@octokit/plugin-paginate-rest").PaginateInterface;
} & import("@octokit/plugin-paginate-graphql").paginateGraphQLInterface & import("@octokit/plugin-rest-endpoint-methods").Api & {
retry: {
retryRequest: (error: import("octokit").RequestError, retries: number, retryAfter: number) => import("octokit").RequestError;
};
};
export { octokit };

View File

@@ -0,0 +1,19 @@
import { llm, llmPrompt } from './llm';
import { fetchUser } from './user';
declare global {
interface Window {
spark: {
llmPrompt: typeof llmPrompt;
llm: typeof llm;
user: typeof fetchUser;
kv: typeof kv;
};
}
}
declare const kv: {
keys: () => Promise<string[]>;
get: <T>(key: string) => Promise<T | undefined>;
set: <T>(key: string, value: T) => Promise<void>;
delete: (key: string) => Promise<void>;
};
export {};

View File

@@ -0,0 +1,8 @@
export interface User {
avatarUrl: string;
email: string;
id: number;
isOwner: boolean;
login: string;
}
export declare function fetchUser(): Promise<null | User>;

View File

@@ -0,0 +1,22 @@
import { type ClassValue } from 'clsx';
export declare function composeEventHandlers<E>(originalEventHandler?: (event: E) => void, ourEventHandler?: (event: E) => void, { checkForDefaultPrevented }?: {
checkForDefaultPrevented?: boolean | undefined;
}): (event: E) => void;
export declare function cn(...inputs: ClassValue[]): string;
export declare function findEventHandlers(props: Record<string, unknown>): string[];
type ContainerStyleProps = {
className?: string;
style?: React.CSSProperties;
};
type SeparatedStyles = {
containerClasses: string;
containerStyles: React.CSSProperties;
innerClasses: string;
innerStyles: React.CSSProperties;
};
/**
* Extracts container-related styles (margin, display, position) from className and style props.
* Returns separated classes and styles for container and inner elements.
*/
export declare function extractContainerStyles({ className, style }: ContainerStyleProps): SeparatedStyles;
export {};

64
packages/spark-tools/dist/llm.js vendored Normal file
View File

@@ -0,0 +1,64 @@
// Earlier versions of our generation recommended models without the prefix
// that GH Models wants. For compatibility, correct those that were on the list explicitly.
const MODEL_FIXES = {
'ai21-jamba-instruct': 'ai21-labs/ai21-jamba-instruct',
'cohere-command-r-plus': 'cohere/cohere-command-r-plus',
'cohere-command-r': 'cohere/cohere-command-r',
'gpt-4o-mini': 'openai/gpt-4o-mini',
'gpt-4o': 'openai/gpt-4o',
'meta-llama-3.1-405b-instruct': 'meta/meta-llama-3.1-405b-instruct',
'meta-llama-3.1-70b-instruct': 'meta/meta-llama-3.1-70b-instruct',
'meta-llama-3.1-8b-instruct': 'meta/meta-llama-3.1-8b-instruct',
'meta-llama-3-70b-instruct': 'meta/meta-llama-3-70b-instruct',
'meta-llama-3-8b-instruct': 'meta/meta-llama-3-8b-instruct',
'mistral-large-2407': 'mistral-ai/mistral-large-2407',
'mistral-large': 'mistral-ai/mistral-large',
'mistral-nemo': 'mistral-ai/mistral-nemo',
'mistral-small': 'mistral-ai/mistral-small',
'phi-3-medium-128K-instruct': 'microsoft/phi-3-medium-128K-instruct',
'phi-3-medium-4K-instruct': 'microsoft/phi-3-medium-4K-instruct',
'phi-3-mini-128K-instruct': 'microsoft/phi-3-mini-128K-instruct',
'phi-3-mini-4K-instruct': 'microsoft/phi-3-mini-4K-instruct',
'phi-3-small-128K-instruct': 'microsoft/phi-3-small-128K-instruct',
'phi-3-small-8K-instruct': 'microsoft/phi-3-small-8K-instruct',
};
const fixModelName = (modelName) => {
if (!modelName)
return 'openai/gpt-4o';
return MODEL_FIXES[modelName] || modelName;
};
async function llm(prompt, modelName, jsonMode) {
const tidiedModelName = fixModelName(modelName);
const response_format = { type: jsonMode ? 'json_object' : 'text' };
const body = {
messages: [
{ role: 'system', content: 'You are a helpful assistant.' },
{ role: 'user', content: prompt },
],
temperature: 1.0,
top_p: 1.0,
max_tokens: 1000,
model: tidiedModelName,
response_format,
};
const response = await fetch('/_spark/llm', {
method: 'POST',
body: JSON.stringify(body),
headers: {
'Content-Type': `application/json`,
},
});
if (!response.ok) {
const errorText = await response.text();
throw new Error(`LLM request failed: ${response.status} ${response.statusText} - ${errorText}`);
}
const data = (await response.json());
const content = data.choices[0].message.content;
return content;
}
function llmPrompt(strings, ...values) {
return strings.reduce((result, str, i) => result + str + (values[i] || ''), '');
}
export { llm, llmPrompt };
//# sourceMappingURL=llm.js.map

1
packages/spark-tools/dist/llm.js.map vendored Normal file
View File

@@ -0,0 +1 @@
{"version":3,"file":"llm.js","sources":["../src/lib/llm.ts"],"sourcesContent":[null],"names":[],"mappings":"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"}

View File

@@ -0,0 +1,2 @@
import type { PluginOption } from 'vite';
export declare const runtimeBuildPlugin: () => PluginOption;

View File

@@ -0,0 +1,2 @@
#!/usr/bin/env node
export {};

69786
packages/spark-tools/dist/runtimeProxy.js vendored Normal file

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

60
packages/spark-tools/dist/spark.js vendored Normal file
View File

@@ -0,0 +1,60 @@
import { E as EventType } from './heartbeat-event-types-BmKuwNhb.js';
import { K as KVClient } from './kv-DBiZoNWq.js';
import { llm, llmPrompt } from './llm.js';
let cachedUser = null;
async function fetchUser() {
try {
if (cachedUser) {
return cachedUser;
}
const response = await fetch('/_spark/user');
cachedUser = await response.json();
return cachedUser;
}
catch (error) {
console.error('Failed to fetch user data:', error);
return null;
}
}
const payload = {
url: window?.location?.href,
load_ms: window?.performance?.now(),
};
window.parent.postMessage({
type: EventType.SPARK_RUNTIME_LOADED,
payload,
}, '*');
fetch('/_spark/loaded', {
method: 'POST',
headers: {
'Content-Type': `application/json`,
},
body: JSON.stringify(payload),
});
const kv = {
keys: async () => {
const client = new KVClient();
return client.getKeys();
},
get: async (key) => {
const client = new KVClient();
return client.getKey(key);
},
set: async (key, value) => {
const client = new KVClient();
return client.setKey(key, value);
},
delete: async (key) => {
const client = new KVClient();
return client.deleteKey(key);
},
};
window.spark = {
llmPrompt,
llm,
user: fetchUser,
kv,
};
//# sourceMappingURL=spark.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"spark.js","sources":["../src/lib/user.ts","../src/lib/spark.ts"],"sourcesContent":[null,null],"names":[],"mappings":";;;;AAQA,IAAI,UAAU,GAAgB,IAAI;AAE3B,eAAe,SAAS,GAAA;AAC7B,IAAA,IAAI;QACF,IAAI,UAAU,EAAE;AACd,YAAA,OAAO,UAAU;QACnB;AAEA,QAAA,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,cAAc,CAAC;AAC5C,QAAA,UAAU,GAAG,MAAM,QAAQ,CAAC,IAAI,EAAE;AAClC,QAAA,OAAO,UAAU;IACnB;IAAE,OAAO,KAAK,EAAE;AACd,QAAA,OAAO,CAAC,KAAK,CAAC,4BAA4B,EAAE,KAAK,CAAC;AAClD,QAAA,OAAO,IAAI;IACb;AACF;;ACPA,MAAM,OAAO,GAAG;AACd,IAAA,GAAG,EAAE,MAAM,EAAE,QAAQ,EAAE,IAAI;AAC3B,IAAA,OAAO,EAAE,MAAM,EAAE,WAAW,EAAE,GAAG,EAAE;CACpC;AAED,MAAM,CAAC,MAAM,CAAC,WAAW,CAAC;IACxB,IAAI,EAAE,SAAS,CAAC,oBAAoB;IACpC,OAAO;CACR,EAAE,GAAG,CAAC;AAEP,KAAK,CAAC,gBAAgB,EAAE;AACtB,IAAA,MAAM,EAAE,MAAM;AACd,IAAA,OAAO,EAAE;AACP,QAAA,cAAc,EAAE,CAAA,gBAAA,CAAkB;AACnC,KAAA;AACD,IAAA,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC;AAC9B,CAAA,CAAC;AAEF,MAAM,EAAE,GAAG;IACT,IAAI,EAAE,YAA8B;AAClC,QAAA,MAAM,MAAM,GAAG,IAAI,QAAQ,EAAE;AAC7B,QAAA,OAAO,MAAM,CAAC,OAAO,EAAE;IACzB,CAAC;AACD,IAAA,GAAG,EAAE,OAAU,GAAW,KAA4B;AACpD,QAAA,MAAM,MAAM,GAAG,IAAI,QAAQ,EAAE;AAC7B,QAAA,OAAO,MAAM,CAAC,MAAM,CAAI,GAAG,CAAC;IAC9B,CAAC;AACD,IAAA,GAAG,EAAE,OAAU,GAAW,EAAE,KAAQ,KAAmB;AACrD,QAAA,MAAM,MAAM,GAAG,IAAI,QAAQ,EAAE;QAC7B,OAAO,MAAM,CAAC,MAAM,CAAI,GAAG,EAAE,KAAK,CAAC;IACrC,CAAC;AACD,IAAA,MAAM,EAAE,OAAO,GAAW,KAAmB;AAC3C,QAAA,MAAM,MAAM,GAAG,IAAI,QAAQ,EAAE;AAC7B,QAAA,OAAO,MAAM,CAAC,SAAS,CAAC,GAAG,CAAC;IAC9B,CAAC;CACF;AAED,MAAM,CAAC,KAAK,GAAG;IACb,SAAS;IACT,GAAG;AACH,IAAA,IAAI,EAAE,SAAS;IACf,EAAE;CACH"}

View File

@@ -0,0 +1,12 @@
{
"name": "app",
"version": "1.0.0",
"main": "index.js",
"type": "module",
"scripts": {
"start": "node proxy.js"
},
"author": "",
"license": "MIT",
"description": "A wrapper for deploying to the GitHub runtime"
}

View File

@@ -0,0 +1,16 @@
import type { PluginOption } from 'vite';
interface SparkPluginOptions {
serverURL?: string;
agentDisabled?: boolean;
projectRoot?: string;
outputDir?: string;
includeProxy?: boolean;
githubRuntimeName?: string;
githubApiUrl?: string;
port?: number;
corsOrigin?: RegExp;
hmrOverlay?: boolean;
logLevel?: 'info' | 'warn' | 'error' | 'silent';
}
export default function sparkVitePlugin(opts?: SparkPluginOptions): PluginOption[];
export {};

View File

@@ -0,0 +1,153 @@
import { randomBytes } from 'crypto';
import sparkAgent from './agentPlugin.js';
import { tagSourcePlugin, designerHost } from './designerPlugin.js';
import { heartbeatPlugin } from './heartbeatPlugin.js';
import createIconImportProxy from './vitePhosphorIconProxyPlugin.js';
import fs from 'fs';
import path from 'path';
import { createRequire } from 'node:module';
import { readFileSync } from 'node:fs';
import 'os';
const require$1 = createRequire(import.meta.url);
const runtimeBuildPlugin = () => {
return {
name: 'runtime-wrapper',
apply: 'build',
generateBundle(options, bundle) {
try {
const runtimeProxyContent = readFileSync(require$1.resolve('./runtimeProxy'), 'utf-8');
this.emitFile({
type: 'asset',
fileName: 'proxy.js',
source: runtimeProxyContent
});
const sparkPackageJsonContent = readFileSync(require$1.resolve('./spark.package.json'), 'utf-8');
this.emitFile({
type: 'asset',
fileName: 'package.json',
source: sparkPackageJsonContent
});
console.log('Emitted proxy files to build output');
}
catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);
console.warn('⚠ Could not resolve files:', errorMessage);
}
}
};
};
function findProjectRoot(startDir = process.cwd()) {
let currentDir = startDir;
while (currentDir !== path.dirname(currentDir)) {
if (fs.existsSync(path.join(currentDir, 'package.json'))) {
return currentDir;
}
currentDir = path.dirname(currentDir);
}
return process.cwd();
}
function readSparkConfig() {
try {
const projectRoot = findProjectRoot();
const configPath = path.join(projectRoot, 'runtime.config.json');
if (fs.existsSync(configPath)) {
const configContent = fs.readFileSync(configPath, 'utf-8');
const config = JSON.parse(configContent);
return config;
}
}
catch (error) {
console.warn('Warning: Could not read spark.json configuration file:', error);
}
return null;
}
const addGitHubAuth = (proxy, options) => {
proxy.on('proxyReq', (proxyReq, req, res) => {
if (process.env.GITHUB_TOKEN) {
proxyReq.setHeader('Authorization', `bearer ${process.env.GITHUB_TOKEN}`);
}
});
};
function sparkVitePlugin(opts = {}) {
const { serverURL = process.env.SPARK_AGENT_URL, agentDisabled = false, includeProxy = true, outputDir = process.env.OUTPUT_DIR || 'dist', githubRuntimeName = readSparkConfig()?.app || process.env.GITHUB_RUNTIME_PERMANENT_NAME, githubApiUrl = process.env.GITHUB_API_URL || 'https://api.github.com', port = 5000, corsOrigin = /^https?:\/\/(?:(?:[^:]+\.)?localhost|127\.0\.0\.1|\[::1\]|(?:.*\.)?github\.com)(?::\d+)?$/, hmrOverlay = false, } = opts;
const runningInWorkbench = !!process.env.SPARK_WORKBENCH_ID;
const plugins = [
createIconImportProxy(),
// Main configuration plugin
{
name: 'spark-config',
config: () => ({
build: {
outDir: outputDir
},
define: {
GITHUB_RUNTIME_PERMANENT_NAME: JSON.stringify(githubRuntimeName),
BASE_KV_SERVICE_URL: JSON.stringify('/_spark/kv'),
VITE_SERVER_SESSION_ID: JSON.stringify(randomBytes(16).toString('hex')),
VITE_WORKBENCH_ORIGIN: JSON.stringify(process.env.SPARK_WORKBENCH_ORIGIN || 'https://github.com'),
},
optimizeDeps: {
include: [
"@phosphor-icons/react",
"@github/spark/hooks",
"sonner",
"react",
"recharts",
"react-day-picker",
"tailwind-merge",
]
},
server: {
port,
hmr: {
overlay: hmrOverlay,
},
cors: {
origin: corsOrigin
},
watch: {
ignored: [
'**/prd.md',
'**.log',
'**/.azcopy/**',
],
awaitWriteFinish: {
pollInterval: 50,
stabilityThreshold: 100,
},
},
proxy: {
'^/_spark/llm': {
target: 'https://models.github.ai/inference/chat/completions',
changeOrigin: true,
ignorePath: true,
configure: addGitHubAuth
},
'^/_spark/.*': {
target: githubApiUrl,
changeOrigin: true,
rewrite: (path) => {
const serviceName = path.replace('/_spark/', '').split('/')[0];
return path.replace(`/_spark/${serviceName}`, `/runtime/${githubRuntimeName}/${serviceName}`);
},
configure: addGitHubAuth
}
},
},
})
}
];
if (includeProxy) {
plugins.push(runtimeBuildPlugin());
}
// Add workbench-specific plugins only when running in workbench
if (runningInWorkbench) {
plugins.unshift(sparkAgent({ serverURL, disabled: agentDisabled }), tagSourcePlugin(), heartbeatPlugin(), designerHost());
}
return plugins;
}
export { sparkVitePlugin as default };
//# sourceMappingURL=sparkVitePlugin.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"sparkVitePlugin.js","sources":["../src/runtime-wrapper/runtimeBuildPlugin.ts","../src/sparkVitePlugin.ts"],"sourcesContent":[null,null],"names":["require"],"mappings":"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"}

View File

@@ -0,0 +1,12 @@
export declare const EventType: {
SPARK_RUNTIME_ERROR: string;
SPARK_RUNTIME_PING: string;
SPARK_RUNTIME_LOADED: string;
SPARK_VITE_WS_CONNECT: string;
SPARK_VITE_WS_DISCONNECT: string;
SPARK_VITE_ERROR: string;
SPARK_VITE_AFTER_UPDATE: string;
ROOT_ELEMENT_STATUS: string;
KV_CLIENT_ERROR: string;
};
export type EventType = (typeof EventType)[keyof typeof EventType];

View File

@@ -0,0 +1,5 @@
export declare const KvEventType: {
SPARK_KV_UPDATED: string;
SPARK_KV_DELETED: string;
};
export type KvEventType = (typeof KvEventType)[keyof typeof KvEventType];

View File

@@ -0,0 +1,2 @@
import type { Plugin } from 'vite';
export default function createIconImportProxy(): Plugin;

View File

@@ -0,0 +1,153 @@
import fs from 'fs';
import path from 'path';
function createIconImportProxy() {
const packageName = '@phosphor-icons/react';
const fallbackIcon = 'Question';
const packagePath = 'node_modules/@phosphor-icons/react';
// Cache to avoid redundant filesystem checks
const existingExportsCache = new Set();
let hasLoadedExports = false;
const proxiedImports = new Map();
// Function to load all available exports from the package
const loadExports = () => {
if (hasLoadedExports)
return;
try {
const packageDir = path.resolve(packagePath);
console.log(`[icon-proxy] Checking for exports in directory: ${packageDir}`);
const filesToCheck = [
path.join(packageDir, 'dist', 'index.js'),
path.join(packageDir, 'dist', 'index.mjs'),
path.join(packageDir, 'dist', 'index.d.ts'),
path.join(packageDir, 'index.js'),
path.join(packageDir, 'index.d.ts'),
];
// Find the first file that exists
const existingFile = filesToCheck.find((file) => fs.existsSync(file));
if (existingFile) {
console.log(`[icon-proxy] Found package file: ${existingFile}`);
const content = fs.readFileSync(existingFile, 'utf-8');
// Extract export names with multiple regex patterns to catch different export styles
const exportPatterns = [
/export\s+\{\s*([^}]+)\s*\}/g, // export { Name1, Name2 }
/export\s+const\s+(\w+)/g, // export const Name
/export\s+function\s+(\w+)/g, // export function Name
/export\s+type\s+(\w+)/g, // export type Name
/export\s+\*\s+from\s+'.*\/csr\/(\w+)'/g, // export * from './csr/Name'
];
exportPatterns.forEach((pattern) => {
let match;
while ((match = pattern.exec(content)) !== null) {
if (pattern.source.includes('\\{')) {
// Multiple exports in braces
const exports$1 = match[1].split(',').map((e) => {
const parts = e.trim().split(/\s+as\s+/);
return parts[parts.length - 1].trim();
});
exports$1.forEach((name) => existingExportsCache.add(name));
}
else {
// Single export
existingExportsCache.add(match[1]);
}
}
});
console.log(`[icon-proxy] Loaded ${existingExportsCache.size} exports from ${existingFile}`);
}
else {
console.warn(`[icon-proxy] Could not find any index files for ${packageName}`);
}
hasLoadedExports = true;
}
catch (error) {
console.error(`[icon-proxy] Error analyzing package exports:`, error);
}
};
return {
name: 'vite-icon-import-proxy',
enforce: 'pre',
configResolved() {
loadExports();
},
transform(code, id) {
// Skip if not a JS/TS file or is in node_modules
if (!/\.(jsx?|tsx?)$/.test(id) || id.includes('node_modules')) {
return null;
}
// Ensure exports are loaded
if (!hasLoadedExports) {
loadExports();
}
if (!existingExportsCache.has(fallbackIcon)) {
// If we don't find the fallback icon, then there is no sense in trying to proxy icons to it.
// It's actually more likely that the icons *do* exist than not, so we should avoid proxying anything to prevent breaking the App's icons.
console.warn('[icon-proxy] Warning: Fallback icon not found. Not proxying any icons');
return null;
}
// No need to process files that don't import from our package
if (!code.includes(packageName)) {
return null;
}
// Regular expression to find imports from the package
// This matches both these patterns:
// import { IconA, IconB } from '@phosphor-icons/react';
// import { IconA as RenamedA, IconB } from '@phosphor-icons/react';
const importRegex = new RegExp(`import\\s+{([^}]+)}\\s+from\\s+['"]${packageName}['"]`, 'g');
let match;
let hasChanges = false;
let modifiedCode = code;
// Process each import statement
while ((match = importRegex.exec(code)) !== null) {
// Extract the imports section { IconA, IconB as AliasB, ... }
const importSection = match[1];
// Split into individual imports and process each
const imports = importSection
.split(',')
.map((item) => item.trim())
.filter((item) => item.length > 0);
let newImports = [...imports];
let needsQuestion = false;
// Process each imported icon
for (let i = 0; i < imports.length; i++) {
const importItem = imports[i];
// Handle aliased imports: IconName as AliasName
const parts = importItem.trim().split(/\s+as\s+/);
const iconName = parts[0].trim();
const aliasName = parts.length > 1 ? parts[1].trim() : iconName;
// Check if this icon exists in the package
if (!existingExportsCache.has(iconName)) {
console.log(`[icon-proxy] Proxying non-existent icon: ${iconName} -> ${fallbackIcon}`);
proxiedImports.set(iconName, fallbackIcon);
// Replace with Question as AliasName
newImports[i] = `${fallbackIcon} as ${aliasName}`;
needsQuestion = true;
hasChanges = true;
}
}
// Make sure Question is included if we need it
if (needsQuestion && !imports.some((imp) => imp === fallbackIcon || imp.startsWith(`${fallbackIcon} `))) {
newImports.push(fallbackIcon);
}
// Construct the new import statement
const originalImport = match[0];
const newImport = `import { ${newImports.join(', ')} } from '${packageName}'`;
// Replace in the code
modifiedCode = modifiedCode.replace(originalImport, newImport);
}
return hasChanges ? modifiedCode : null;
},
// At the end of the build, report which icons were proxied
closeBundle() {
if (proxiedImports.size > 0) {
console.log('\n[icon-proxy] Proxied imports:');
proxiedImports.forEach((fallback, original) => {
console.log(` - "${original}" -> "${fallback}"`);
});
}
},
};
}
export { createIconImportProxy as default };
//# sourceMappingURL=vitePhosphorIconProxyPlugin.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"vitePhosphorIconProxyPlugin.js","sources":["../src/vitePhosphorIconProxyPlugin.ts"],"sourcesContent":[null],"names":["exports"],"mappings":"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"}

View File

@@ -0,0 +1,119 @@
{
"name": "@github/spark",
"version": "0.0.1",
"type": "module",
"scripts": {
"build": "rollup --config rollup.config.ts --configPlugin @rollup/plugin-typescript",
"prepack": "MODE=PACKAGE npm run build",
"test": "vitest run",
"test:watch": "vitest"
},
"exports": {
"./agent-plugin": {
"types": "./dist/agentPlugin.d.ts",
"import": "./dist/agentPlugin.js"
},
"./db": {
"types": "./dist/lib/db.d.ts",
"import": "./dist/db.js"
},
"./designer-styles.css": "./dist/designer-styles.css",
"./designerHost": {
"types": "./dist/designerHost.d.ts",
"import": "./dist/designerHost.js"
},
"./designerPlugin": {
"types": "./dist/designerPlugin.d.ts",
"import": "./dist/designerPlugin.js"
},
"./heartbeat": {
"types": "./dist/heartbeat/heartbeat.d.ts",
"import": "./dist/heartbeat.js"
},
"./heartbeatPlugin": {
"types": "./dist/heartbeat/heartbeatPlugin.d.ts",
"import": "./dist/heartbeatPlugin.js"
},
"./hooks": {
"types": "./dist/hooks/index.d.ts",
"import": "./dist/index.js"
},
"./initializeTelemetry": {
"types": "./dist/heartbeat/heartbeat.d.ts",
"import": "./dist/heartbeat.js"
},
"./llm": {
"types": "./dist/lib/llm.d.ts",
"import": "./dist/llm.js"
},
"./logToFileLogger": {
"types": "./dist/heartbeat/logToFileLogger.d.ts",
"import": "./dist/logToFileLogger.js"
},
"./package.json": "./package.json",
"./spark": {
"types": "./dist/lib/spark.d.ts",
"import": "./dist/spark.js"
},
"./spark-vite-plugin": {
"types": "./dist/sparkVitePlugin.d.ts",
"import": "./dist/sparkVitePlugin.js"
},
"./telemetryPlugin": {
"types": "./dist/heartbeat/heartbeatPlugin.d.ts",
"import": "./dist/heartbeatPlugin.js"
},
"./vitePhosphorIconProxyPlugin": {
"types": "./dist/vitePhosphorIconProxyPlugin.d.ts",
"import": "./dist/vitePhosphorIconProxyPlugin.js"
}
},
"prettier": {
"semi": false,
"singleQuote": true,
"trailingComma": "all",
"printWidth": 120
},
"files": [
"LICENSE",
"dist",
"package.json"
],
"devDependencies": {
"@rollup/plugin-commonjs": "^28.0.3",
"@rollup/plugin-json": "^6.1.0",
"@rollup/plugin-node-resolve": "^16.0.0",
"@rollup/plugin-replace": "^6.0.2",
"@rollup/plugin-terser": "^0.4.4",
"@rollup/plugin-typescript": "^12.1.2",
"@testing-library/jest-dom": "^6.6.3",
"@testing-library/react": "^16.0.1",
"@types/body-parser": "^1.19.6",
"@types/express": "^5.0.1",
"@types/node": "^22.13.9",
"@types/react": "^19.0.0",
"jsdom": "^25.0.1",
"rollup": "^4.35.0",
"rollup-plugin-delete": "^3.0.1",
"tslib": "^2.8.1",
"ulid": "^3.0.0",
"vitest": "^3.0.9",
"zod": "^3.24.2"
},
"peerDependencies": {
"react": "^19.0.0",
"vite": "^7.0.0 || ^6.4.1"
},
"author": "",
"license": "MIT",
"keywords": [],
"description": "",
"publishConfig": {
"access": "public"
},
"dependencies": {
"body-parser": "^1.20.3",
"express": "^5.2.0",
"octokit": "^5.0.3"
}
}