Editor/Launcher/ProjectCreator.cs
using System;
using System.IO;
using System.Linq;
using System.Text.Json;

/// <summary>
/// Creates a minimal s&amp;box project on disk.
/// </summary>
public static class ProjectCreator
{
	/// <summary>
	/// Creates <paramref name="location"/>/<paramref name="name"/> with an .sbproj and Code/Assets folders.
	/// Returns the path to the new .sbproj. Throws <see cref="InvalidOperationException"/> with a user-facing message on bad input.
	/// </summary>
	public static string Create( string name, string location, string type )
	{
		name = name?.Trim();
		location = location?.Trim();

		if ( string.IsNullOrEmpty( name ) )
			throw new InvalidOperationException( "Project name can't be empty." );

		if ( name.IndexOfAny( Path.GetInvalidFileNameChars() ) >= 0 )
			throw new InvalidOperationException( "Project name contains characters that aren't allowed in a folder name." );

		if ( string.IsNullOrEmpty( location ) || !Directory.Exists( location ) )
			throw new InvalidOperationException( "Location folder doesn't exist." );

		var ident = ToIdent( name );
		if ( ident.Length == 0 )
			throw new InvalidOperationException( "Project name needs at least one letter or number." );

		var root = Path.Combine( location, name );
		if ( Directory.Exists( root ) )
			throw new InvalidOperationException( $"A folder named '{name}' already exists there." );

		Directory.CreateDirectory( root );
		Directory.CreateDirectory( Path.Combine( root, "Code" ) );
		Directory.CreateDirectory( Path.Combine( root, "Assets" ) );

		var config = new
		{
			Title = name,
			Type = type,
			Org = "local",
			Ident = ident,
			Schema = 1,
			IncludeSourceFiles = false,
			Resources = (string)null,
			PackageReferences = Array.Empty<string>(),
			EditorReferences = (string)null,
			Mounts = (string)null,
			IsStandaloneOnly = false,
			Metadata = new { }
		};

		var sbproj = Path.Combine( root, $"{ident}.sbproj" );
		File.WriteAllText( sbproj, JsonSerializer.Serialize( config, new JsonSerializerOptions { WriteIndented = true } ) );
		return sbproj;
	}

	/// <summary>
	/// "My Cool Game" -> "mycoolgame"
	/// </summary>
	public static string ToIdent( string name ) => new string( name.ToLowerInvariant().Where( c => c is (>= 'a' and <= 'z') or (>= '0' and <= '9') or '_' ).ToArray() );
}