StarterSetCreatePublishMojo.java

/*
 * Copyright © 2026 IKE Network (support@ike.network)
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package network.ike.plugin;

import org.apache.maven.api.Session;
import org.apache.maven.api.di.Inject;
import org.apache.maven.api.plugin.MojoException;
import org.apache.maven.api.plugin.annotations.Mojo;
import org.apache.maven.api.plugin.annotations.Parameter;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.UUID;

/**
 * Genesis of a new starter-set project — the executing half of the
 * {@code ike:starter-set-create-*} pair (IKE-Network/ike-issues#866): mints the set's
 * permanent identity (unless supplied), materializes the five-module family from the
 * standards templates, bootstrap-commits, and proves itself by building the generated
 * project green before reporting success.
 *
 * <p>Run {@code ike:starter-set-create-draft} first — publish executes exactly what
 * the draft showed. Genesis runs once: the target directory must not already exist.
 *
 * @since 235
 */
@Mojo(name = IkeGoal.NAME_STARTER_SET_CREATE_PUBLISH, projectRequired = false)
public class StarterSetCreatePublishMojo extends StarterSetCreateParameters
        implements org.apache.maven.api.plugin.Mojo {

    /** Creates this goal instance. */
    public StarterSetCreatePublishMojo() {}

    @Inject
    private org.apache.maven.api.plugin.Log log;

    @Inject
    private Session session;

    /**
     * Skip the post-generation verification build (offline use); the generated
     * project must then be built manually before first use.
     */
    @Parameter(property = "skipVerify", defaultValue = "false")
    boolean skipVerify;

    /**
     * Mints, materializes, commits, and verifies the new starter-set project.
     *
     * @throws MojoException if the target exists, templates cannot be loaded, files
     *                       cannot be written, or the verification build fails
     */
    @Override
    public void execute() {
        StarterSetPlan plan = plan(UUID.randomUUID().toString(), log, session);
        Map<String, String> templates = StarterSetCreateSupport.loadTemplates(session);
        Path target = targetDirectory(plan);
        if (Files.exists(target)) {
            throw new MojoException("Genesis runs once — the target already exists: " + target
                    + ". Conformance of existing projects is the scaffold system's job.");
        }

        List<String> written = new ArrayList<>();
        try {
            for (Map.Entry<String, String> template : templates.entrySet()) {
                String relative = plan.targetPath(template.getKey());
                Path file = target.resolve(relative);
                Files.createDirectories(file.getParent());
                Files.writeString(file, plan.expand(template.getValue()), StandardCharsets.UTF_8);
                written.add(relative);
            }
        } catch (IOException e) {
            throw new MojoException("Cannot materialize the starter-set project at " + target, e);
        }

        run(target, "git", "init", "-q", "-b", "main");
        List<String> add = new ArrayList<>(List.of("git", "add"));
        add.addAll(written);
        run(target, add.toArray(new String[0]));
        run(target, "git", "commit", "-q", "-m",
                "Genesis of " + plan.token("artifactRoot") + " (" + plan.token("setName") + ")\n\n"
                        + "Generated by ike:starter-set-create-publish. Set identity (permanent):\n"
                        + plan.token("setUuid")
                        + "\n\nRefs: IKE-Network/ike-issues#866");

        if (skipVerify) {
            log.warn("Verification build skipped (-DskipVerify) — build the project before first use.");
        } else {
            log.info("Verifying the generated project (mvn test) ...");
            run(target, Path.of(System.getProperty("maven.home"), "bin", "mvn").toString(),
                    "test", "-q");
            log.info("Verification build green.");
        }

        for (String line : StarterSetCreateSupport
                .report(plan, templates, target, false).split("\n")) {
            log.info(line);
        }
        log.info("");
        log.info("SET IDENTITY MINTED (permanent, recorded in " + plan.token("className")
                + ".java): " + plan.token("setUuid"));
    }

    private void run(Path directory, String... command) {
        try {
            Process process = new ProcessBuilder(command)
                    .directory(directory.toFile())
                    .redirectErrorStream(true)
                    .start();
            try (BufferedReader reader = new BufferedReader(
                    new InputStreamReader(process.getInputStream(), StandardCharsets.UTF_8))) {
                String line = reader.readLine();
                while (line != null) {
                    log.info("[create] " + line);
                    line = reader.readLine();
                }
            }
            int exit = process.waitFor();
            if (exit != 0) {
                throw new MojoException("Genesis step failed (exit " + exit + "): "
                        + String.join(" ", command));
            }
        } catch (IOException e) {
            throw new MojoException("Cannot run genesis step: " + String.join(" ", command), e);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            throw new MojoException("Interrupted during genesis step", e);
        }
    }
}