diff --git a/.env.example b/.env.example
index 213e132..9194c4d 100644
--- a/.env.example
+++ b/.env.example
@@ -6,3 +6,6 @@ NODE_ENV=development
# Leaving this empty will generate a new unique random session secret at start
SESSION_SECRET=
+
+# Change if your nf cli executable isn't in the path
+NF_CLI_PATH=nf
diff --git a/.idea/prettier.xml b/.idea/prettier.xml
index 0c83ac4..8c024db 100644
--- a/.idea/prettier.xml
+++ b/.idea/prettier.xml
@@ -3,5 +3,7 @@
+
+
\ No newline at end of file
diff --git a/src/hooks.server.ts b/src/hooks.server.ts
index 22b59c5..0894943 100644
--- a/src/hooks.server.ts
+++ b/src/hooks.server.ts
@@ -15,7 +15,11 @@ const sessionHandle = sveltekitSessionHandle({
});
const checkAuthorizationHandle: Handle = async ({ event, resolve }) => {
- if (!event.locals.session.data.path && event.url.pathname !== '/load-project') {
+ if (
+ !event.locals.session.data.path &&
+ event.url.pathname !== '/load-project' &&
+ event.url.pathname + event.url.search !== '/cli?/createProject'
+ ) {
throw redirect(302, '/load-project');
}
return resolve(event);
diff --git a/src/lib/server/utils/cli/cli-error.ts b/src/lib/server/utils/cli/cli-error.ts
new file mode 100644
index 0000000..834113b
--- /dev/null
+++ b/src/lib/server/utils/cli/cli-error.ts
@@ -0,0 +1,7 @@
+export class CliError extends Error {
+ message: string;
+ constructor(message: string) {
+ super();
+ this.message = message;
+ }
+}
diff --git a/src/lib/server/utils/cli/cli-interface.ts b/src/lib/server/utils/cli/cli-interface.ts
new file mode 100644
index 0000000..ce12da1
--- /dev/null
+++ b/src/lib/server/utils/cli/cli-interface.ts
@@ -0,0 +1,52 @@
+import { env } from '$env/dynamic/private';
+import { CliError } from '@utils-server/cli/cli-error';
+import child_process from 'node:child_process';
+
+export class CliInterface {
+ private readonly projectPath: string;
+
+ constructor(projectPath: string) {
+ this.projectPath = projectPath;
+ }
+
+ createProject(
+ projectName: string,
+ packageManager: 'npm' | 'yarn' | 'pnpm' | 'bun',
+ language: 'js' | 'ts',
+ strictTypeChecking: boolean,
+ multiplayerServer: boolean,
+ skipDependencyInstallation: boolean,
+ dockerContainerization: boolean,
+ ) {
+ this.runCli([
+ `new`,
+ `-d`,
+ this.projectPath,
+ `--name`,
+ projectName,
+ `--package-manager`,
+ packageManager,
+ `--language`,
+ language,
+ strictTypeChecking ? '--strict' : '--no-strict',
+ multiplayerServer ? '--server' : '--no-server',
+ skipDependencyInstallation ? '--skip-install' : '--no-skip-install',
+ dockerContainerization ? '--docker' : '--no-docker',
+ ]);
+ }
+
+ startProject() {
+ this.runCli([`build`, `-d`, this.projectPath]);
+ this.runCli([`start`, `-d`, this.projectPath]);
+ }
+
+ private runCli(params: string[]) {
+ const res = child_process.spawnSync(env.NF_CLI_PATH, params);
+ if (res.status === null) {
+ throw new CliError(`Executable ${env.NF_CLI_PATH} cannot be found or executed`);
+ }
+ if (res.status !== 0) {
+ throw new CliError(res.stderr.toString());
+ }
+ }
+}
diff --git a/src/routes/cli/+page.server.ts b/src/routes/cli/+page.server.ts
new file mode 100644
index 0000000..6eb50a8
--- /dev/null
+++ b/src/routes/cli/+page.server.ts
@@ -0,0 +1,61 @@
+import { fail } from '@sveltejs/kit';
+import { CliError } from '@utils-server/cli/cli-error';
+import { CliInterface } from '@utils-server/cli/cli-interface';
+
+import type { Actions } from './$types';
+
+export const actions = {
+ // Create project
+ // Run project
+ // Export project
+ createProject: async ({ request }) => {
+ const data = await request.json();
+
+ if (!data.projectPath) {
+ return fail(403, { success: false, errorMsg: "Missing arg: 'projectPath'" });
+ }
+ if (!data.projectName) {
+ return fail(403, { success: false, errorMsg: "Missing arg: 'projectName'" });
+ }
+ if (!data.packageManager) {
+ return fail(403, { success: false, errorMsg: "Missing arg: 'packageManager'" });
+ }
+ if (!data.language) {
+ return fail(403, { success: false, errorMsg: "Missing arg: 'language'" });
+ }
+
+ try {
+ new CliInterface(data.projectPath).createProject(
+ data.projectName,
+ data.packageManager,
+ data.language,
+ data.strictTypeChecking,
+ data.multiplayerServer,
+ data.skipDependencyInstallation,
+ data.dockerContainerization,
+ );
+ return {
+ success: true,
+ };
+ } catch (e: unknown) {
+ if (e instanceof CliError) {
+ return fail(403, { success: false, errorMsg: e.message });
+ }
+ throw e;
+ }
+ },
+
+ startProject: async ({ locals }) => {
+ try {
+ new CliInterface(locals.session.data.path).startProject();
+ return {
+ success: true,
+ };
+ } catch (e: unknown) {
+ if (e instanceof CliError) {
+ return fail(403, { success: false, errorMsg: e.message });
+ }
+ throw e;
+ }
+ },
+} satisfies Actions;
diff --git a/src/routes/cli/+page.svelte b/src/routes/cli/+page.svelte
new file mode 100644
index 0000000..34cd7bd
--- /dev/null
+++ b/src/routes/cli/+page.svelte
@@ -0,0 +1,27 @@
+
+
+
diff --git a/src/routes/load-project/+page.server.ts b/src/routes/load-project/+page.server.ts
index f2607a1..32c01ee 100644
--- a/src/routes/load-project/+page.server.ts
+++ b/src/routes/load-project/+page.server.ts
@@ -28,7 +28,15 @@ export const load: PageServerLoad = async ({ url, cookies, locals }) => {
}
absoluteProjectPath = (await serverProjectPath.json())['projectPath'];
} else {
- return { success: false, errorMsg: 'No project provided' };
+ return {
+ success: false,
+ creationPanel: env.API_URL ? 'api' : 'local',
+ errorMsg: `No project provided: ${
+ env.API_URL
+ ? 'Go back to the NanoForge project manager to access a project'
+ : 'Select or create a local project'
+ }`,
+ };
}
try {
diff --git a/src/routes/load-project/+page.svelte b/src/routes/load-project/+page.svelte
index 585933a..960e6f1 100644
--- a/src/routes/load-project/+page.svelte
+++ b/src/routes/load-project/+page.svelte
@@ -1,9 +1,10 @@
@@ -13,7 +14,88 @@
- {data.success ? 'Project loading' : 'Error: ' + data.errorMsg}
+ {#if !data.success}
+
+ {'Error: ' + data.errorMsg}
+
+ {/if}
+ {#if form?.errorMsg}
+
+ {'Error: ' + form?.errorMsg}
+
+ {/if}
+ {#if data.creationPanel === 'local'}
+
+
+ {/if}