Skip to content
Merged
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
21 changes: 20 additions & 1 deletion docs/reference/native-config-compiler.md
Original file line number Diff line number Diff line change
@@ -1,11 +1,30 @@
# Native config compiler foundation

This is an experimental, pure compiler for a bounded subset of the planned
`.hack/hack.project.json` format. It does not discover projects, execute workloads,
`.hack/hack.project.json` format. The compiler does not discover projects, execute workloads,
import Compose, migrate data, decrypt environment values, perform host admission,
or change how existing projects run. A successful compile is syntax and semantic
validation, not backend capability or application acceptance.

The CLI recognizes this filename as a project boundary. Native runtime and adoption
are not enabled yet: legacy project commands refuse with
`E_NATIVE_PROJECT_UNSUPPORTED`. If active `.hack/` or `.dev/` Compose, JSON or TOML
inputs also exist, they refuse with `E_NATIVE_PROJECT_CONFLICT`. The marker still
blocks fallback when malformed, a future version, a directory or a dangling link.
Filesystem inspection failures also refuse. Generated internal files, branch files,
backups and a root-level Compose file are not competing authored Hack inputs.

Discovery never crosses a native boundary to select an ancestor Compose project.
Registered name/ID lookup checks the stored root directly. Project listing marks
blocked registered entries `unavailable`, includes `input_diagnostic`, and continues
listing other entries without parsing the blocked legacy configuration. Global
configuration and explicit offline validation remain available. Init, legacy env
repair, config writes and domain migration/rollback refuse before modifying native
input; a refused rollback preserves its recovery journal. Selection is checked
again at mutation boundaries, but this is not atomic protection against concurrent
external edits. Locked adoption belongs to a later integration step.
`hack env get` retains its fixed redacted failure message and empty stdout.

The standalone `packages/config-compiler` Rust package has no dependency on the
native runtime, virtualization, Docker, or platform provider APIs. It uses the
repository's pinned Rust 1.97.1 and committed Cargo lockfile when building. The
Expand Down
11 changes: 11 additions & 0 deletions src/commands/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import { ensureDir, readTextFile, writeTextFileIfChanged } from "../lib/fs.ts";
import { isRecord } from "../lib/guards.ts";
import type { ProjectContext } from "../lib/project.ts";
import { findProjectContext } from "../lib/project.ts";
import { assertLegacyProjectDirectory } from "../lib/project-input-selection.ts";
import { normalizeProjectName } from "../lib/project-name.ts";
import {
resolveRegisteredProjectByName,
Expand Down Expand Up @@ -187,6 +188,10 @@ const handleConfigSet: CommandHandlerFor<typeof configSetSpec> = async ({
const nextText = `${JSON.stringify(read.value, null, 2)}\n`;
if (project.scope === "global") {
await ensureDir(dirname(read.path));
} else {
await assertLegacyProjectDirectory({
projectDir: project.project.projectDir,
});
}
const result = await writeTextFileIfChanged(read.path, nextText);

Expand Down Expand Up @@ -329,6 +334,9 @@ async function readConfigObject(opts: {
opts.target.project.projectDir,
PROJECT_CONFIG_FILENAME
);
await assertLegacyProjectDirectory({
projectDir: opts.target.project.projectDir,
});
const jsonText = await readTextFile(jsonPath);
if (jsonText !== null) {
const parsed = parseJsonObject({ text: jsonText, path: jsonPath });
Expand Down Expand Up @@ -381,6 +389,9 @@ async function readConfigJsonForSet(opts: {
opts.target.project.projectDir,
PROJECT_CONFIG_FILENAME
);
await assertLegacyProjectDirectory({
projectDir: opts.target.project.projectDir,
});
const jsonText = await readTextFile(jsonPath);
if (jsonText === null) {
const tomlPath = resolve(
Expand Down
9 changes: 9 additions & 0 deletions src/commands/doctor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,7 @@ import {
repairLegacyComposeEnvFileReferences,
resolveProjectEnvConfig,
} from "../lib/project-env-config.ts";
import { assertLegacyProjectDiscovery } from "../lib/project-input-selection.ts";
import {
inspectProjectLifecycleHygiene,
repairProjectLifecycleSessions,
Expand Down Expand Up @@ -443,6 +444,9 @@ async function maybeRunNativeMappingRecovery(
const handleDoctor: CommandHandlerFor<typeof doctorSpec> = async ({
args,
}): Promise<number> => {
await assertLegacyProjectDiscovery({
startDir: resolveDoctorStartDir(args.options.path),
});
const mappingResult = await maybeRunNativeMappingRecovery(args);
if (mappingResult !== null) {
return mappingResult;
Expand Down Expand Up @@ -889,6 +893,10 @@ const handleDoctor: CommandHandlerFor<typeof doctorSpec> = async ({
return 0;
};

function resolveDoctorStartDir(pathOption: string | undefined): string {
return resolve(process.cwd(), pathOption ?? ".");
}

async function handleNativeDoctor(opts: {
readonly runtime: NativeRuntimeSelection;
readonly browser: ReturnType<typeof parseBrowserNetworkOptions>;
Expand Down Expand Up @@ -3181,6 +3189,7 @@ async function maybeMigrateProjectEnvConfig(opts: {

const removed = await removeLegacyProjectEnvArtifacts({
paths: cleanupCandidates,
projectRoot: project.projectRoot,
});
if (removed.length > 0) {
note(`Removed ${removed.join(", ")}`, "env cleanup");
Expand Down
Loading
Loading