sculptui-vscode
Version:
Visually edit your React component code from the browser
205 lines (191 loc) • 7.37 kB
text/typescript
import fs, { stat } from 'fs';
import path from 'path';
import { commands, ExtensionContext, ExtensionMode, Uri } from 'vscode';
import StatusBar from './status-bar/StatusBar';
import DevServer, { DevServerStatus } from './server/DevServer';
import { getProjectFolder, getWorkspaceFolder, traverseSubFolders, isWorkspacesRoot } from './utils/folders';
import { detectScriptsVersion } from '@sculpt-ui/server';
import SculptServer, { SculptServerStatus } from '@sculpt-ui/source-server/lib/SculptServer';
import telemetry from './telemetry';
import { Request, RequestHandler, Response } from 'express';
import { SourceRef } from '@sculpt-ui/core/lib/types';
import { selectCode } from './utils/vscode-file';
import { log, logError } from './utils/vscode-output';
process.env.REACT_EDITOR = 'code';
let statusBar: StatusBar;
let sculptServer: SculptServer;
let devServer: DevServer | undefined = undefined;
let gStoragePath = '';
const urlPathPrefix = '/_sculpt';
const localDevMode: string = 'dev-local';
const clientHandler: (x: string) => RequestHandler = (assetsFolder: string) => (req: Request, res: Response) => {
let reqPathBase = urlPathPrefix;
if (req.params.key && req.params.key !== 'static') {
reqPathBase += `/${req.params.key}`;
}
const subPath = req.originalUrl.substr(reqPathBase.length + 1);
const filePath = path.resolve(assetsFolder, 'sculpt-client', subPath);
fs.access(filePath, fs.constants.F_OK, err => {
if (err) {
if (req.accepts(['html', 'text/html'])) {
res.sendFile('sculpt-client/index.html', { root: assetsFolder });
} else {
res.status(404).end();
}
} else {
res.sendFile(`sculpt-client/${subPath}`, { root: assetsFolder });
}
});
};
const extensionsHandler: (x: string, packagesFolder: string) => RequestHandler = (
assetsFolder: string,
packagesFolder: string,
) => (req: Request, res: Response) => {
const subPath = req.originalUrl.substr(req.originalUrl.indexOf('/extensions'));
if (process.env.mode === localDevMode && subPath.startsWith('extension/styles/dist')) {
res.sendFile(subPath.replace('extension/dist', 'extension'), {
root: packagesFolder,
});
} else {
res.sendFile(subPath, { root: assetsFolder });
}
};
const ideService = {
getServerInfo: async () => ({
rootPath: process.cwd(),
message: 'Sculpt is alive!',
ide: 'VS Code',
}),
selectInEditor: async (id: string, sourceRef: SourceRef) => {
selectCode(
sourceRef.filePath,
{
line: sourceRef.endLine ? sourceRef.endLine - 1 : sourceRef.line,
character: sourceRef.endColumn ? sourceRef.endColumn - 1 : 0,
},
{ line: sourceRef.line - 1, character: sourceRef.column ? sourceRef.column - 1 : 0 },
);
},
};
export async function activate(context: ExtensionContext) {
const { subscriptions, extensionPath, globalStoragePath } = context;
gStoragePath = globalStoragePath;
telemetry.activate(context);
telemetry.addCommonProperties({ extensionMode: ExtensionMode[context.extensionMode] });
telemetry.sendEvent('activate');
if (!statusBar) {
statusBar = new StatusBar();
statusBar.subscribe(subscriptions);
}
const assetsFolder = Uri.file(path.join(extensionPath, 'assets')).fsPath;
const packagesFolder = Uri.file(path.join(extensionPath, '../packages')).fsPath;
// IDEServer always activated
const workspaceFolder = getWorkspaceFolder();
log(process.cwd());
if (!sculptServer) {
sculptServer = new SculptServer({
clientHandler: clientHandler(assetsFolder),
ideService: ideService,
onLog: message => log(message),
showErrorMessage: message => logError(message),
extensionsHandler: extensionsHandler(assetsFolder, packagesFolder),
onStatusChanged: (status: SculptServerStatus) => statusBar.setConnected(status === SculptServerStatus.connected),
});
}
const sculptServerUrl = await sculptServer.start();
if (globalStoragePath && workspaceFolder) {
if (!fs.existsSync(globalStoragePath)) {
fs.mkdirSync(globalStoragePath, { recursive: true });
}
const settingsFile = path.join(globalStoragePath, 'sculpt-ide.json');
/* TODO: Use a lock file here */
let ideConfig: { [folder: string]: string } = {};
if (fs.existsSync(settingsFile)) {
try {
ideConfig = JSON.parse(fs.readFileSync(settingsFile, 'utf8'));
} catch (err) {
console.error(
'Could not correctly read settings file. Presumably corrupt and will be overwritten.',
settingsFile,
);
}
}
ideConfig[workspaceFolder] = sculptServerUrl;
fs.writeFileSync(settingsFile, JSON.stringify(ideConfig, null, 2), 'utf8');
}
const folder = getProjectFolder(undefined, workspaceFolder);
if (!folder) {
return;
}
const rootDetectedScript = detectScriptsVersion(folder);
let scriptsAvailable = isWorkspacesRoot(folder) ? rootDetectedScript === 'sculpt' : !!rootDetectedScript;
if (!scriptsAvailable) {
traverseSubFolders(
folder,
(dir: string) => {
const detected = !!detectScriptsVersion(dir);
if (detected) {
scriptsAvailable = true;
}
return detected;
},
3,
['node_modules', '.vscode', '.git'],
);
}
if (scriptsAvailable) {
statusBar.setRunAvailable(true);
if (!devServer) {
devServer = new DevServer(
extensionPath,
sculptServerUrl,
status => {
statusBar.setRunning(status === DevServerStatus.started || status === DevServerStatus.stopping);
statusBar.setInTransition(status === DevServerStatus.starting || status === DevServerStatus.stopping);
},
(message: string, duration?: number) => {
statusBar.setTemporaryMessage(message, duration);
},
);
}
const start = async () => {
const result = await devServer?.start();
if (result) sculptServer.tryToInitProject(result);
};
subscriptions.push(
commands.registerCommand('sculpt-ui.start', start),
commands.registerCommand('sculpt-ui.stop', devServer.stop),
);
}
}
// this method is called when your extension is deactivated
export function deactivate() {
const workspaceFolder = getWorkspaceFolder();
if (gStoragePath && workspaceFolder) {
if (!fs.existsSync(gStoragePath)) {
fs.mkdirSync(gStoragePath, { recursive: true });
}
const settingsFile = path.join(gStoragePath, 'sculpt-ide.json');
let ideConfig: { [folder: string]: string } = {};
if (fs.existsSync(settingsFile)) {
try {
ideConfig = JSON.parse(fs.readFileSync(settingsFile, 'utf8'));
} catch (err) {
console.error(
'Could not correctly read settings file. Presumably corrupt and will be overwritten.',
settingsFile,
);
}
}
delete ideConfig[workspaceFolder];
fs.writeFileSync(settingsFile, JSON.stringify(ideConfig, null, 2), 'utf8');
if (devServer) {
devServer.stop();
}
if (sculptServer) {
sculptServer.stop();
}
telemetry.sendEvent('deactivate');
telemetry.deactivate();
}
}