Skip to content
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -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
2 changes: 2 additions & 0 deletions .idea/prettier.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 5 additions & 1 deletion src/hooks.server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
7 changes: 7 additions & 0 deletions src/lib/server/utils/cli/cli-error.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export class CliError extends Error {
message: string;
constructor(message: string) {
super();
this.message = message;
}
}
52 changes: 52 additions & 0 deletions src/lib/server/utils/cli/cli-interface.ts
Original file line number Diff line number Diff line change
@@ -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());
}
}
}
61 changes: 61 additions & 0 deletions src/routes/cli/+page.server.ts
Original file line number Diff line number Diff line change
@@ -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;
27 changes: 27 additions & 0 deletions src/routes/cli/+page.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<script lang="ts">
import { resolve } from '$app/paths';
import Logo from '$lib/assets/logo.png';
</script>

<div class="h-screen flex flex-col gap-1">
<header class="h-16 flex bg-neutral-900">
<div class="h-full w-full flex">
<a href={resolve('/')} class="h-full px-3 pb-1 pt-2">
<img src={Logo} alt="Logo" class="h-full rounded-full" />
</a>
<div class="h-full w-full flex flex-col justify-between">
<form
onsubmit={(e) => {
e.preventDefault();
fetch('/cli?/startProject', {
method: 'POST',
body: JSON.stringify({}),
});
}}
>
<input type="submit" value="Start Project" />
</form>
</div>
</div>
</header>
</div>
10 changes: 9 additions & 1 deletion src/routes/load-project/+page.server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
88 changes: 85 additions & 3 deletions src/routes/load-project/+page.svelte
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
<script lang="ts">
import { resolve } from '$app/paths';
import Logo from '$lib/assets/logo.png';
import type { PageProps } from './$types';
import type { ActionData, PageData } from './$types';
import { goto } from '$app/navigation';

let { data }: PageProps = $props();
let { data, form }: { data: PageData; form: ActionData } = $props();
</script>

<div class="h-screen flex flex-col gap-1">
Expand All @@ -13,7 +14,88 @@
<img src={Logo} alt="Logo" class="h-full rounded-full" />
</a>
<div class="h-full w-full flex flex-col justify-between">
{data.success ? 'Project loading' : 'Error: ' + data.errorMsg}
{#if !data.success}
<div style="white-space:pre; color:red">
{'Error: ' + data.errorMsg}
</div>
{/if}
{#if form?.errorMsg}
<div style="white-space:pre; color:red">
{'Error: ' + form?.errorMsg}
</div>
{/if}
{#if data.creationPanel === 'local'}
<form
onsubmit={(e) => {
e.preventDefault();
const formData = new FormData(e.currentTarget);
// eslint-disable-next-line svelte/no-navigation-without-resolve
goto(`/load-project?projectPath=${formData.get('projectPath')}`);
}}
>
<input name="projectPath" placeholder="Project path" />
<input type="submit" value="Go to project" />
</form>
<form
onsubmit={(e) => {
e.preventDefault();
const formData = new FormData(e.currentTarget);
fetch('/cli?/createProject', {
method: 'POST',
body: JSON.stringify({
projectPath: formData.get('projectPath'),
projectName: formData.get('projectName'),
packageManager: formData.get('packageManager'),
language: formData.get('language'),
strictTypeChecking: formData.get('strictTypeChecking') ?? false,
multiplayerServer: formData.get('multiplayerServer') ?? false,
skipDependencyInstallation: formData.get('skipDependencyInstallation') ?? false,
dockerContainerization: formData.get('dockerContainerization') ?? false,
}),
});
}}
>
<input name="projectPath" placeholder="Project local path" />
<input name="projectName" placeholder="Project Name" />
<label for="packageManagerId">Package manager :</label>
<select name="packageManager" id="packageManagerId">
<option value="npm">npm</option>
<option value="yarn">yarn</option>
<option value="pnpm">pnpm</option>
<option value="bun">bun</option>
</select>
<label for="languageId">Project language :</label>
<select name="language" id="languageId">
<option value="js">js</option>
<option value="ts">ts</option>
</select>

<label for="strictTypeCheckingId">Strict Type Checking :</label>
<input
type="checkbox"
name="strictTypeChecking"
value="true"
id="strictTypeCheckingId"
/>
<label for="multiplayerServerId">Multiplayer Server :</label>
<input type="checkbox" name="multiplayerServer" value="true" id="multiplayerServerId" />
<label for="skipDependencyInstallationId">Skip Dependency Installation :</label>
<input
type="checkbox"
name="skipDependencyInstallation"
value="true"
id="skipDependencyInstallationId"
/>
<label for="dockerContainerizationId">Docker containerization :</label>
<input
type="checkbox"
name="dockerContainerization"
value="true"
id="dockerContainerizationId"
/>
<button type="submit" value="Create Local Project">Create Local Project</button>
</form>
{/if}
</div>
</div>
</header>
Expand Down
Loading