Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
15 commits
Select commit Hold shift + click to select a range
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
2 changes: 1 addition & 1 deletion eslint.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ const layersAbove = {
// The files other modules of a layer may import; everything else in a module is internal.
const modules = {
domain: {
build: ["build-service"],
build: ["build", "build-service"],
config: ["config", "config-service"],
init: ["init-service"],
roblox: ["roblox", "supported-services"],
Expand Down
33 changes: 15 additions & 18 deletions src/commands/build/__tests__/build-command.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { DisposableStore } from "../../../base/disposable.js";
import { CoreCommandService } from "../../../platform/commands/core-command-service.js";
import {
MockConfigService,
brokenEntry,
mockEntry,
} from "../../../domain/config/__tests__/mock-config-service.js";
import { BuildService } from "../../../domain/build/build-service.js";
Expand Down Expand Up @@ -359,15 +360,13 @@ describe("build command", () => {

it("should refuse to build when a config is invalid", async () => {
const logService = new NullLogService();
const entry = mockEntry({}, undefined, {
diagnostics: [
errorDiagnostic(
"config.unknownField",
{ resource: "/repo/default.rogen.json" },
"boom."
),
],
});
const entry = brokenEntry([
errorDiagnostic(
"config.unknownField",
{ resource: "/repo/default.rogen.json" },
"boom."
),
]);

const result = await run(new MockConfigService([entry]), logService);

Expand Down Expand Up @@ -583,15 +582,13 @@ describe("build command", () => {
});

it("should fail without printing when a config is invalid, for the caller to report", async () => {
const entry = mockEntry({}, undefined, {
diagnostics: [
errorDiagnostic(
"config.unknownField",
{ resource: "/repo/default.rogen.json" },
"boom."
),
],
});
const entry = brokenEntry([
errorDiagnostic(
"config.unknownField",
{ resource: "/repo/default.rogen.json" },
"boom."
),
]);

const { result, logService } = await buildJson(
new MockConfigService([entry])
Expand Down
49 changes: 18 additions & 31 deletions src/commands/build/__tests__/build-log.test.ts
Original file line number Diff line number Diff line change
@@ -1,26 +1,26 @@
import path from "path";
import {
BuildSummary,
ConfigBuild,
} from "../../../domain/build/build-service.js";
import { ConfigEntry } from "../../../domain/config/config-service.js";
import { BuildSummary, ConfigBuild } from "../../../domain/build/build.js";
import { ResolvedConfig } from "../../../domain/config/config.js";
import { errorDiagnostic } from "../../../platform/diagnostics/diagnostic.js";
import { MockLogService } from "../../../platform/log/__tests__/mock-log-service.js";
import { LogLevel } from "../../../platform/log/log-service.js";
import { mockConfig } from "../../../domain/config/__tests__/mock-config-service.js";
import {
ResolvedConfigSpec,
mockConfig,
} from "../../../domain/config/__tests__/mock-config-service.js";
import { BuildLog } from "../build-log.js";

const cwd = path.resolve("/repo");

const debugLines = (
dir: string,
entry: ConfigEntry,
config: ResolvedConfig,
summary?: BuildSummary
): string[] => {
const logService = new MockLogService();
logService.setLevel(LogLevel.Debug);
const log = new BuildLog(logService, dir);
log.outcome(entry, summary ? builtOf(summary) : failed, []);
log.outcome({ ...(summary ? builtOf(summary) : failed), config }, []);
return logService.entries
.filter(({ kind }) => kind === "debug")
.map(({ text }) => text);
Expand All @@ -47,15 +47,8 @@ const builtOf = (
readFiles: [],
});

const entryOf = (overrides: Partial<ConfigEntry> = {}): ConfigEntry =>
new ConfigEntry({
file: path.join(cwd, "match.rogen.json"),
chain: [path.join(cwd, "match.rogen.json")],
resolved: undefined,
diagnostics: [],
skippedTags: [],
...overrides,
});
const configOf = (spec: ResolvedConfigSpec = {}): ResolvedConfig =>
mockConfig({ file: path.join(cwd, "match.rogen.json"), ...spec });

const summaryOf = (overrides: Partial<BuildSummary> = {}): BuildSummary => ({
roots: [],
Expand All @@ -67,23 +60,22 @@ const summaryOf = (overrides: Partial<BuildSummary> = {}): BuildSummary => ({
...overrides,
});

const describeConfig = (entry: ConfigEntry, dir: string) =>
debugLines(dir, entry);
const describeConfig = (config: ResolvedConfig, dir: string) =>
debugLines(dir, config);

const describeBuild = (summary: BuildSummary, dir: string) =>
debugLines(dir, entryOf(), summary);
debugLines(dir, configOf(), summary);

describe("BuildLog config lines", () => {
it("should say nothing about a config with no parent and no skipped tags", () => {
expect(describeConfig(entryOf(), cwd)).toEqual([]);
expect(describeConfig(configOf(), cwd)).toEqual([]);
});

it("should name the extends chain relative to the working directory", () => {
expect(
describeConfig(
entryOf({
chain: [
path.join(cwd, "match.rogen.json"),
configOf({
parents: [
path.join(cwd, "default.rogen.json"),
path.join(cwd, "shared/base.rogen.json"),
],
Expand All @@ -95,7 +87,7 @@ describe("BuildLog config lines", () => {

it("should name each tag flag the config does not declare", () => {
expect(
describeConfig(entryOf({ skippedTags: ["mock", "debug"] }), cwd)
describeConfig(configOf({ skippedTags: ["mock", "debug"] }), cwd)
).toEqual([
"tag mock skipped: not declared in this config",
"tag debug skipped: not declared in this config",
Expand Down Expand Up @@ -185,12 +177,7 @@ describe("BuildLog.outcome", () => {
note?: string
) => {
const logService = new MockLogService();
new BuildLog(logService, cwd).outcome(
entryOf(),
build,
diagnostics,
note
);
new BuildLog(logService, cwd).outcome(build, diagnostics, note);
return logService.entries.map(({ kind, text }) => [kind, text]);
};

Expand Down
8 changes: 1 addition & 7 deletions src/commands/build/__tests__/build-rojo.test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import fs from "fs";
import path from "path";
import "../build-command.js";
import { DisposableStore } from "../../../base/disposable.js";
import { BuildService } from "../../../domain/build/build-service.js";
import { ConfigService } from "../../../domain/config/config-service.js";
import { CoreConfigService } from "../../../domain/config/core-config-service.js";
Expand All @@ -24,7 +23,6 @@ import { NullLogService } from "../../../platform/log/null-log-service.js";
import { buildServiceOf } from "../../../domain/build/__tests__/fixtures.js";

describeWithRojo("build command against Rojo", () => {
let store: DisposableStore;
let dir: string;

const write = (file: string, content: string) => {
Expand All @@ -37,9 +35,7 @@ describeWithRojo("build command against Rojo", () => {
const environment = new NativeEnvironmentService(args, dir);
const fileSystem = new DiskFileSystemService();
const logService = new NullLogService();
const configService = store.add(
new CoreConfigService(fileSystem, environment)
);
const configService = new CoreConfigService(fileSystem, environment);

const services = new ServiceCollection();
services.set(ConfigService, configService);
Expand All @@ -56,12 +52,10 @@ describeWithRojo("build command against Rojo", () => {
};

beforeEach(() => {
store = new DisposableStore();
dir = makeRojoDir("rogen-rojo-");
});

afterEach(() => {
store[Symbol.dispose]();
fs.rmSync(dir, { recursive: true, force: true });
});

Expand Down
48 changes: 24 additions & 24 deletions src/commands/build/build-command.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,9 @@
import { ReportedError } from "../../base/errors.js";
import { formatJsonDocument } from "../../base/json.js";
import { Result, err, ok } from "../../base/result.js";
import { BuildService, ConfigBuild } from "../../domain/build/build-service.js";
import {
ConfigService,
ResolvedEntry,
configRefsFromArgs,
} from "../../domain/config/config-service.js";
import { ConfigBuild } from "../../domain/build/build.js";
import { BuildService } from "../../domain/build/build-service.js";
import { ConfigService } from "../../domain/config/config-service.js";
import {
AbstractCommand,
registerCommand,
Expand Down Expand Up @@ -61,43 +58,46 @@ registerCommand(
const logService = accessor.get(LogService);
const cwd = accessor.get(EnvironmentService).cwd;

const selection = await configService.initialize(
configRefsFromArgs(args, args._.slice(1))
);
const selection = await configService.select(args);
if (selection.isErr()) return selection;

const targets = buildService.requireBuildable(selection.value);
if (targets.isErr()) return targets;
const builds = await buildService.build(selection.value, {
checkSyncDir: true,
});
if (builds.isErr()) return builds;

const builds = await buildService.run(
targets.value.map(({ config }) => config),
{ checkSyncDir: true }
);
const errors = builds.flatMap((build) => build.errors);
const errors = builds.value.flatMap((build) => build.errors);
const { unselected } = selection.value;
return args.json
? this.reportAsJson(logService, builds, errors, unselected)
? this.reportAsJson(
logService,
builds.value,
errors,
unselected
)
: this.report(
new BuildLog(logService, cwd),
targets.value,
builds,
builds.value,
errors,
unselected
);
}

private report(
log: BuildLog,
targets: readonly ResolvedEntry[],
builds: readonly ConfigBuild[],
errors: readonly Diagnostic[],
unselected: readonly string[]
): Result<void, Error> {
log.begin("build", targets, unselected);
log.begin(
"build",
builds.map(({ config }) => config),
unselected
);

for (const [index, build] of builds.entries()) {
if (builds.length > 1) log.heading(targets[index]);
log.outcome(targets[index].entry, build, warningsOf(build));
for (const build of builds) {
if (builds.length > 1) log.heading(build.config);
log.outcome(build, warningsOf(build));
}
if (errors.length > 0) return err(new DiagnosticsError(errors));

Expand Down
28 changes: 12 additions & 16 deletions src/commands/build/build-log.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,17 @@
import path from "path";
import { relativeTo } from "../../base/path.js";
import { plural } from "../../base/strings.js";
import { BuildSummary, ConfigBuild } from "../../domain/build/build-service.js";
import {
ConfigEntry,
ResolvedEntry,
} from "../../domain/config/config-service.js";
import { BuildSummary, ConfigBuild } from "../../domain/build/build.js";
import { ResolvedConfig } from "../../domain/config/config.js";
import { Diagnostic } from "../../platform/diagnostics/diagnostic.js";
import { LogService } from "../../platform/log/log-service.js";

/** The `extends` chain and skipped tag flags of a config. */
function describeConfig(entry: ConfigEntry, cwd: string): string[] {
const parents = entry.parents.map((file) => relativeTo(cwd, file));
function describeConfig(config: ResolvedConfig, cwd: string): string[] {
const parents = config.parents.map((file) => relativeTo(cwd, file));
return [
...(parents.length > 0 ? [`extends: ${parents.join(" -> ")}`] : []),
...entry.skippedTags.map(
...config.skippedTags.map(
(tag) => `tag ${tag} skipped: not declared in this config`
),
];
Expand Down Expand Up @@ -68,11 +65,11 @@ export class BuildLog {
/** Opens the output: the command, the configs it builds and the ones it leaves out. */
begin(
command: string,
targets: readonly ResolvedEntry[],
configs: readonly ResolvedConfig[],
unselected: readonly string[]
): void {
this.logService.intro(
`rogen ${command} · ${targets.map(({ config }) => config.label).join(", ")}`
`rogen ${command} · ${configs.map(({ label }) => label).join(", ")}`
);
if (unselected.length > 0) {
this.logService.info(
Expand All @@ -82,13 +79,12 @@ export class BuildLog {
}

/** Heads the lines about one config, when a run builds several. */
heading({ config }: ResolvedEntry): void {
heading(config: ResolvedConfig): void {
this.logService.step(config.label);
}

/** One config's line for what the run did to its project file, ending in `note` if given, then `diagnostics`. */
outcome(
entry: ConfigEntry,
build: ConfigBuild,
diagnostics: readonly Diagnostic[],
note?: string
Expand All @@ -101,12 +97,12 @@ export class BuildLog {
case "wrote":
case "unchanged":
this.logService.success(line(build.outcome));
this.details(entry, build.summary);
this.details(build.config, build.summary);
break;
case "notWritten":
case "failed":
this.logService.error(line("not written"));
this.details(entry);
this.details(build.config);
break;
}
this.diagnostics(diagnostics);
Expand All @@ -123,9 +119,9 @@ export class BuildLog {
}

/** The `--verbose` lines for one config: how it was loaded and, once built, what the build placed. */
private details(entry: ConfigEntry, summary?: BuildSummary): void {
private details(config: ResolvedConfig, summary?: BuildSummary): void {
for (const line of [
...describeConfig(entry, this.cwd),
...describeConfig(config, this.cwd),
...(summary ? describeBuild(summary, this.cwd) : []),
])
this.logService.debug(line);
Expand Down
2 changes: 1 addition & 1 deletion src/commands/build/build-report.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { toNative } from "../../base/path.js";
import { ConfigBuild } from "../../domain/build/build-service.js";
import { ConfigBuild } from "../../domain/build/build.js";
import { diagnosticToJson } from "../../platform/diagnostics/diagnostic.js";

type BuildOutcome = "wrote" | "unchanged" | "notWritten";
Expand Down
Loading
Loading