Skip to content
Open
Show file tree
Hide file tree
Changes from all 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