initproj API

initproj

package

API reference for the initproj package.

F
function

InitProject

InitProject creates a new rfw project from the embedded template.

Parameters

projectName
string
skipTidy
bool

Returns

err
error
cmd/rfw/initproj/init.go:20-91
func InitProject(projectName string, skipTidy bool) (err error)

{
	if err := validateModulePath(projectName); err != nil {
		return err
	}

	moduleName := projectName
	projectName = path.Base(moduleName)

	projectPath := projectName

	if _, err := os.Stat(projectPath); !os.IsNotExist(err) {
		return fmt.Errorf("project directory already exists")
	}

	if err := os.Mkdir(projectPath, 0o750); err != nil {
		return fmt.Errorf("failed to create project directory: %w", err)
	}
	projectRoot, err := os.OpenRoot(projectPath)
	if err != nil {
		return fmt.Errorf("open project directory: %w", err)
	}
	defer func() {
		if closeErr := projectRoot.Close(); err == nil {
			err = closeErr
		}
	}()

	err = fs.WalkDir(TemplatesFS, "template", func(path string, d fs.DirEntry, err error) error {
		if err != nil {
			return err
		}

		if path == "template" {
			return nil
		}

		relPath := strings.TrimPrefix(path, "template/")
		targetPath := strings.TrimSuffix(relPath, ".tmpl")

		if d.IsDir() {
			if err := projectRoot.MkdirAll(targetPath, 0o755); err != nil {
				return err
			}
			return nil
		}

		content, err := TemplatesFS.ReadFile(path)
		if err != nil {
			return err
		}

		contentStr := string(content)
		contentStr = strings.ReplaceAll(contentStr, "{{moduleName}}", moduleName)
		contentStr = strings.ReplaceAll(contentStr, "{{projectName}}", projectName)

		return writeRootFile(projectRoot, targetPath, []byte(contentStr))
	})
	if err != nil {
		return fmt.Errorf("failed to copy template files: %w", err)
	}

	if err := copyWasmExec(projectPath); err != nil {
		return fmt.Errorf("failed to copy wasm_exec.js: %w", err)
	}

	if err := initGoModule(projectPath, moduleName, skipTidy); err != nil {
		return fmt.Errorf("failed to initialize go module: %w", err)
	}

	fmt.Printf("Project '%s' initialized successfully.\n", projectName)
	return nil
}
F
function

copyWasmExec

Parameters

projectDir
string

Returns

err
error
cmd/rfw/initproj/init.go:93-145
func copyWasmExec(projectDir string) (err error)

{
	cmd := exec.Command("go", "env", "GOROOT")
	output, err := cmd.Output()
	if err != nil {
		return fmt.Errorf("failed to get GOROOT: %w", err)
	}
	goRoot := strings.TrimSpace(string(output))

	candidates := []struct {
		root string
		file string
	}{
		{root: filepath.Join(goRoot, "lib"), file: filepath.Join("wasm", "wasm_exec.js")},
		{root: filepath.Join(goRoot, "misc"), file: filepath.Join("wasm", "wasm_exec.js")},
	}
	var input []byte
	for _, candidate := range candidates {
		resolvedRoot, resolveErr := filepath.EvalSymlinks(candidate.root)
		if resolveErr != nil {
			continue
		}
		goRootDirectory, openErr := os.OpenRoot(resolvedRoot)
		if openErr != nil {
			continue
		}
		input, err = goRootDirectory.ReadFile(candidate.file)
		closeErr := goRootDirectory.Close()
		if err == nil && closeErr == nil {
			break
		}
		if err == nil {
			return fmt.Errorf("failed to close Go root: %w", closeErr)
		}
	}
	if input == nil {
		return fmt.Errorf("wasm_exec.js not found in GOROOT (%s); ensure Go is properly installed", goRoot)
	}

	destinationRoot, err := os.OpenRoot(projectDir)
	if err != nil {
		return fmt.Errorf("failed to open project directory: %w", err)
	}
	defer func() {
		if closeErr := destinationRoot.Close(); err == nil {
			err = closeErr
		}
	}()
	if err := writeRootFile(destinationRoot, "wasm_exec.js", input); err != nil {
		return fmt.Errorf("failed to write wasm_exec.js: %w", err)
	}

	return nil
}
F
function

initGoModule

Parameters

projectPath
string
moduleName
string
skipTidy
bool

Returns

err
error
cmd/rfw/initproj/init.go:147-174
func initGoModule(projectPath, moduleName string, skipTidy bool) (err error)

{
	if err := validateModulePath(moduleName); err != nil {
		return err
	}
	goMod := fmt.Sprintf("module %s\n\ngo %s\n", moduleName, scaffoldGoVersion)
	projectRoot, err := os.OpenRoot(projectPath)
	if err != nil {
		return fmt.Errorf("open project directory: %w", err)
	}
	defer func() {
		if closeErr := projectRoot.Close(); err == nil {
			err = closeErr
		}
	}()
	if err := writeRootFile(projectRoot, "go.mod", []byte(goMod)); err != nil {
		return fmt.Errorf("write go.mod: %w", err)
	}

	if !skipTidy {
		cmd := exec.Command("go", "mod", "tidy")
		cmd.Dir = projectPath
		if out, err := cmd.CombinedOutput(); err != nil {
			return fmt.Errorf("go mod tidy failed: %w: %s", err, strings.TrimSpace(string(out)))
		}
	}

	return nil
}
F
function

validateModulePath

Parameters

modulePath
string

Returns

error
cmd/rfw/initproj/init.go:176-204
func validateModulePath(modulePath string) error

{
	if modulePath == "" {
		return fmt.Errorf("project name cannot be empty")
	}
	if strings.TrimSpace(modulePath) != modulePath || strings.HasPrefix(modulePath, "-") {
		return fmt.Errorf("invalid module path %q", modulePath)
	}
	for _, char := range modulePath {
		if char >= 'a' && char <= 'z' ||
			char >= 'A' && char <= 'Z' ||
			char >= '0' && char <= '9' ||
			strings.ContainsRune("-._/", char) {
			continue
		}
		return fmt.Errorf("invalid character %q in module path", char)
	}
	for _, segment := range strings.Split(modulePath, "/") {
		if segment == "" || segment == "." || segment == ".." {
			return fmt.Errorf("invalid module path segment %q", segment)
		}
		if !isASCIILetterOrDigit(rune(segment[0])) || !isASCIILetterOrDigit(rune(segment[len(segment)-1])) {
			return fmt.Errorf("module path segment %q must start and end with a letter or digit", segment)
		}
		if isReservedPathName(segment) {
			return fmt.Errorf("module path segment %q is reserved", segment)
		}
	}
	return nil
}
F
function

isASCIILetterOrDigit

Parameters

char
rune

Returns

bool
cmd/rfw/initproj/init.go:206-210
func isASCIILetterOrDigit(char rune) bool

{
	return char >= 'a' && char <= 'z' ||
		char >= 'A' && char <= 'Z' ||
		char >= '0' && char <= '9'
}
F
function

isReservedPathName

Parameters

segment
string

Returns

bool
cmd/rfw/initproj/init.go:212-221
func isReservedPathName(segment string) bool

{
	base := strings.ToUpper(strings.SplitN(segment, ".", 2)[0])
	switch base {
	case "CON", "PRN", "AUX", "NUL":
		return true
	}
	return len(base) == 4 &&
		(base[:3] == "COM" || base[:3] == "LPT") &&
		base[3] >= '1' && base[3] <= '9'
}
F
function

writeRootFile

Parameters

root
path
string
data
[]byte

Returns

error
cmd/rfw/initproj/init.go:223-232
func writeRootFile(root *os.Root, path string, data []byte) error

{
	file, err := root.OpenFile(path, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0o644)
	if err != nil {
		return err
	}
	if _, err := file.Write(data); err != nil {
		return errors.Join(err, file.Close())
	}
	return file.Close()
}
F
function

TestInitProjectSuccess

TestInitProjectSuccess verifies project scaffolding.

Parameters

cmd/rfw/initproj/initproj_test.go:13-60
func TestInitProjectSuccess(t *testing.T)

{
	dir := t.TempDir()
	t.Chdir(dir)

	if err := InitProject("example.com/testproj", true); err != nil {
		t.Fatalf("InitProject failed: %v", err)
	}
	projDir := filepath.Join(dir, "testproj")
	if _, err := os.Stat(filepath.Join(projDir, "go.mod")); err != nil {
		t.Fatalf("go.mod not created: %v", err)
	}
	if _, err := os.Stat(filepath.Join(projDir, "wasm_exec.js")); err != nil {
		t.Fatalf("wasm_exec.js not created: %v", err)
	}
	if _, err := os.Stat(filepath.Join(projDir, "wasm_loader.js")); err != nil {
		t.Fatalf("wasm_loader.js not created: %v", err)
	}
	projectRoot, err := os.OpenRoot(projDir)
	if err != nil {
		t.Fatalf("open project root: %v", err)
	}
	t.Cleanup(func() {
		if err := projectRoot.Close(); err != nil {
			t.Errorf("close project root: %v", err)
		}
	})
	index, err := projectRoot.ReadFile("index.html")
	if err != nil {
		t.Fatalf("read index.html: %v", err)
	}
	if strings.Contains(string(index), "Date.now()") ||
		!strings.Contains(string(index), "RFW_WASM_VERSION") {
		t.Fatalf("index.html does not use the generated wasm version")
	}
	loader, err := projectRoot.ReadFile("wasm_loader.js")
	if err != nil {
		t.Fatalf("read wasm_loader.js: %v", err)
	}
	if !strings.Contains(string(loader), "instantiateStreaming") {
		t.Fatalf("wasm_loader.js does not use streaming instantiation")
	}
	if !strings.Contains(string(loader), "handleRuntimeExit") ||
		!strings.Contains(string(loader), "rfw:runtime-reload") ||
		!strings.Contains(string(loader), "writeRecovery") ||
		!strings.Contains(string(loader), "reloadOnExit = true") {
		t.Fatalf("wasm_loader.js does not include bounded runtime recovery")
	}
}
F
function

TestInitProjectErrors

TestInitProjectErrors checks basic error paths.

Parameters

cmd/rfw/initproj/initproj_test.go:63-76
func TestInitProjectErrors(t *testing.T)

{
	if err := InitProject("", true); err == nil {
		t.Fatalf("expected error for empty project name")
	}

	dir := t.TempDir()
	t.Chdir(dir)
	if err := os.Mkdir("exists", 0o750); err != nil {
		t.Fatalf("Mkdir failed: %v", err)
	}
	if err := InitProject("exists", true); err == nil {
		t.Fatalf("expected error for existing directory")
	}
}
F
function

TestValidateModulePath

Parameters

cmd/rfw/initproj/initproj_test.go:78-99
func TestValidateModulePath(t *testing.T)

{
	for _, modulePath := range []string{"example", "example.com/team/app", "example.com/team/app/v2"} {
		if err := validateModulePath(modulePath); err != nil {
			t.Errorf("expected %q to be valid: %v", modulePath, err)
		}
	}
	for _, modulePath := range []string{
		"",
		"-x",
		"../app",
		"example.com//app",
		"example.com/app name",
		"example.com/app\nreplace x",
		"example.com/app+tools",
		"example.com/.hidden",
		"example.com/NUL.txt",
	} {
		if err := validateModulePath(modulePath); err == nil {
			t.Errorf("expected %q to be rejected", modulePath)
		}
	}
}