* Copyright (c) 2025 Huawei Device Co., Ltd.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import * as vscode from 'vscode';
import * as fs from 'fs';
import * as path from 'path';
import { exec, spawn } from 'child_process';
import { getIDEType, setCjmpSdkRoot, cjmpDoctorWithSpawn, getDefaultShell } from './utils';
import {
isValidCjmpSdkRoot,
resolveCjmpSdkRoot,
resolveKeelsCommand,
} from './cjmpSdk';
export const PROJECT_TEMPLATE_OPTIONS = ['app', 'library', 'logic-module', 'module'];
export async function createCjmpProject(context: vscode.ExtensionContext): Promise<void> {
const isSdkReady = await executeSDK(context);
if (!isSdkReady) {
return;
}
await createProjectTemplate();
}
* Set SDK-related information
*/
export async function executeSDK(context: vscode.ExtensionContext): Promise<boolean> {
if (!resolveCjmpSdkRoot()) {
const workspaceSdkPath = vscode.workspace
.getConfiguration('CJMP')
.inspect<string>('sdkPath')
?.workspaceValue
?.trim();
const sdkSettingTarget = workspaceSdkPath
? vscode.ConfigurationTarget.Workspace
: vscode.ConfigurationTarget.Global;
const sdkChoose = await vscode.window.showInformationMessage(
'Please provide the root path of your CJMP SDK.',
'Ok',
'Cancel'
);
if (!sdkChoose || sdkChoose === 'Cancel') {
return false;
}
const localPathUri = await vscode.window.showOpenDialog({
canSelectFolders: true,
canSelectFiles: false,
canSelectMany: false,
openLabel: 'Select Folder for CJMP SDK'
});
if (localPathUri && localPathUri.length > 0) {
const localPath = localPathUri[0].fsPath;
if (!fs.existsSync(localPath)) {
vscode.window.showErrorMessage('Invalid path provided or path does not exist.');
return false;
}
if (!isValidCjmpSdkRoot(localPath)) {
vscode.window.showErrorMessage(
`The selected folder is not a valid CJMP SDK root: "${localPath}". `
+ 'Select the SDK root containing cjmp-tools/bin, cjmp-tools/third_party, and cjmp-tools/tools.',
);
return false;
}
await setCjmpSdkRoot(localPath, sdkSettingTarget);
vscode.window.showInformationMessage(`CJMP SDK root set to: ${localPath}. Running "keels doctor -v" ...`);
await cjmpDoctorWithSpawn();
} else {
return false;
}
}
return true;
}
* select the path and create a CJMP project.
*/
async function createProjectTemplate(): Promise<void> {
const localPathUri = await vscode.window.showOpenDialog({
canSelectFolders: true,
canSelectFiles: false,
canSelectMany: false,
openLabel: 'Select Folder for CJMP Project'
});
if (localPathUri && localPathUri.length > 0) {
const localPath = localPathUri[0].fsPath;
if (fs.existsSync(localPath)) {
const projectTemplate = await vscode.window.showQuickPick(PROJECT_TEMPLATE_OPTIONS, {
placeHolder: 'Select the template',
});
if (!projectTemplate) {
return;
}
let logicModuleType;
if (projectTemplate === 'logic-module') {
const logicModuleTypes = ['native', 'keels'];
logicModuleType = await vscode.window.showQuickPick(logicModuleTypes, {
placeHolder: 'Select the logic-module type',
});
if (!logicModuleType) {
return;
}
}
const projectName = await vscode.window.showInputBox({
prompt: 'Please enter the name of your project',
placeHolder: 'e.g., my_cjmp_project'
});
if (projectName && projectTemplate) {
try {
await createProject(localPath, projectName, projectTemplate, logicModuleType);
vscode.window.showInformationMessage(`Running "keels create ${projectName}". Please wait...`);
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);
vscode.window.showErrorMessage(`Failed to create project: ${errorMessage}`);
}
} else {
vscode.window.showErrorMessage('Project name and template type are required.');
}
} else {
vscode.window.showErrorMessage('Invalid path provided or path does not exist.');
}
} else {
vscode.window.showErrorMessage('No folder selected.');
}
}
const createProject =
async (localPath: string, projectName: string, projectTemplate: string, logicModuleType: string | undefined): Promise<void> =>
{
return new Promise(async (resolve, reject) => {
try {
const keelsCommand = resolveKeelsCommand();
if (!keelsCommand) {
resolve();
return;
}
const command = getCreateProjectCommand(
keelsCommand.executablePath,
projectName,
projectTemplate,
logicModuleType,
);
if (!command) {
return;
}
const outputChannel = vscode.window.createOutputChannel('CJMP Output');
outputChannel.show(true);
const createProcess = process.platform === 'win32'
? spawn('cmd', ['/c', command], { cwd: localPath, env: keelsCommand.environment })
: spawn(getDefaultShell(), ['-c', command], {
cwd: localPath.replace(/\\/g, '/'),
env: keelsCommand.environment,
});
createProcess.stdout.on('data', (data) => {
const message = data.toString();
outputChannel.append(message);
});
createProcess.stderr.on('data', (data) => {
const errorMessage = data.toString();
outputChannel.append(errorMessage);
});
createProcess.on('close', async (code) => {
if (code === 0) {
vscode.window.showInformationMessage(`Project "${projectName}" created successfully!`);
const projectPath = path.join(localPath, projectName);
await openProjectAndFile(projectPath);
resolve();
} else {
vscode.window.showErrorMessage(`Failed to create project. Exit code: ${code}`);
reject(new Error(`Failed to create project. Exit code: ${code}`));
}
});
createProcess.on('error', (error) => {
vscode.window.showErrorMessage(`Failed to execute command: ${error.message}`);
outputChannel.appendLine(`Error: ${error.message}`);
reject(error);
});
} catch (error) {
vscode.window.showErrorMessage(`Error creating project: ${error instanceof Error ? error.message : String(error)}`);
reject(error);
}
});
};
export function getCreateProjectCommand(
keelsCommand: string,
projectName: string,
projectTemplate: string,
logicModuleType: string | undefined,
): string | undefined {
switch (projectTemplate) {
case 'logic-module':
return `${keelsCommand} create -t ${projectTemplate} --app-type ${logicModuleType} ${projectName}`;
case 'app':
case 'library':
case 'module':
return `${keelsCommand} create -t ${projectTemplate} ${projectName}`;
default:
return undefined;
}
}
async function openProjectAndFile(projectPath: string) {
try {
const uri = vscode.Uri.file(projectPath);
writeLockFile(projectPath);
await vscode.commands.executeCommand('vscode.openFolder', uri, false);
} catch (error) {
vscode.window.showErrorMessage(
`Failed to open project: ${error instanceof Error ? error.message : String(error)}`
);
}
}
function writeLockFile(projectPath: string) {
const lockPath = path.join(projectPath, '.vscode', '.CJMP_create.lock');
console.log(lockPath);
const dir = path.dirname(lockPath);
if (!fs.existsSync(dir)) {
fs.mkdirSync(dir, { recursive: true });
}
fs.writeFileSync(lockPath, Date.now().toString());
}