Skip to content

Commit 0adad48

Browse files
committed
no-mistakes(review): Count owned runtime adapter in undo; gate api-docs entity symbols
1 parent d466e97 commit 0adad48

18 files changed

Lines changed: 314 additions & 72 deletions

File tree

‎CHANGELOG.md‎

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -220,9 +220,11 @@ here.**
220220
It is now written only when `modelBaseUrl` is set.
221221

222222
- **TypeScript API docs describe only what the run writes.** The `api-docs` pages listed the
223-
stock queries and Fastify routes for every entity, whatever the generator list held. A project
224-
that wires neither now gets pages without them, and an owned generator that writes them
225-
counts when it carries the `emitsQueries` or `mountsRoutes` marker.
223+
stock entity module (the model, its Zod schemas and `relations()`), queries and Fastify routes
224+
for every entity, whatever the generator list held. A project that wires none of them, such as
225+
the `meta init --with-docs` scaffold, now gets pages without them. An owned generator that
226+
writes one counts when it carries the `emitsEntityModule`, `emitsQueries` or `mountsRoutes`
227+
marker.
226228

227229
- **The metamodel reference index reads as prose.** The generated index said "NOT documentation"
228230
in capitals and told a reader of an HTML page to "follow a link into `types/<family>.md`". It

‎server/csharp/MetaObjects.Cli.Tests/EjectUndoTests.cs‎

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,4 +153,36 @@ public void Gen_notice_names_identical_copies_and_the_undo_command_and_is_silent
153153
Assert.DoesNotContain("entity", notice);
154154
Assert.Contains("dotnet meta eject --undo", notice);
155155
}
156+
157+
[Fact]
158+
public void A_changed_helper_runtime_keeps_its_generator_out_of_the_notice_and_out_of_no_name_undo()
159+
{
160+
Eject("routes");
161+
Assert.Contains("routes", OwnedCopy.IdenticalNotice(_root)!);
162+
var runtimeFile = $"{HelperRuntime.OwnedDirectory}/{HelperRuntime.Files[0]}";
163+
Edit(runtimeFile);
164+
165+
Assert.Null(OwnedCopy.IdenticalNotice(_root));
166+
var result = EjectCommand.Undo([], _root, force: false);
167+
Assert.Equal(0, result.ExitCode);
168+
Assert.True(File.Exists(Owned("codegen/generators/RoutesGenerator.cs")));
169+
Assert.True(File.Exists(Owned(runtimeFile)));
170+
171+
var named = EjectCommand.Undo(["routes"], _root, force: false);
172+
Assert.Equal(2, named.ExitCode);
173+
Assert.Contains(HelperRuntime.Files[0], string.Join("\n", named.Err));
174+
Assert.True(File.Exists(Owned("codegen/generators/RoutesGenerator.cs")));
175+
}
176+
177+
[Fact]
178+
public void Undoing_the_last_runtime_user_removes_the_whole_identical_helper_runtime()
179+
{
180+
Eject("routes");
181+
182+
var result = EjectCommand.Undo([], _root, force: false);
183+
184+
Assert.Equal(0, result.ExitCode);
185+
Assert.False(Directory.Exists(Owned(HelperRuntime.OwnedDirectory)));
186+
Assert.Contains(result.Out, l => l.StartsWith("The helper runtime copy is gone"));
187+
}
156188
}

‎server/csharp/MetaObjects.Cli/EjectCommand.cs‎

Lines changed: 5 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -252,7 +252,8 @@ public static Outcome Run(IReadOnlyList<string> names, string root, bool force)
252252
/// <paramref name="names"/> every identical owned copy goes; with names only those, and a copy that
253253
/// DIFFERS is refused (exit 2, nothing removed) unless <paramref name="force"/>. Every name is
254254
/// validated before anything is deleted. Companion emitters go only when identical (or forced) and
255-
/// no remaining owned generator is handed them; the helper runtime likewise. It never edits
255+
/// no remaining owned generator is handed them. The helper runtime counts toward a copy being
256+
/// identical, so it goes whole when no remaining owned generator's output imports it. It never edits
256257
/// codegen/Generators.cs or the project: it prints the edit that points each entry back at the
257258
/// shipped generator.
258259
/// </summary>
@@ -269,7 +270,7 @@ public static Outcome Undo(IReadOnlyList<string> names, string root, bool force)
269270
return new Outcome(0, [owned.Count == 0
270271
? "no owned generator copies under codegen/generators/; nothing to undo."
271272
: "no owned copy is identical to its shipped generator, so nothing was removed: " +
272-
string.Join("; ", owned.Select(e => $"{e.Name} {OwnedCopy.Status(root, e)}")) + "."], []);
273+
string.Join("; ", owned.Select(e => $"{e.Name} {OwnedCopy.UndoStatus(root, e)}")) + "."], []);
273274
}
274275
}
275276
else
@@ -285,11 +286,11 @@ public static Outcome Undo(IReadOnlyList<string> names, string root, bool force)
285286
if (notOwned.Count > 0)
286287
return Outcome.Usage(
287288
$"no owned copy of: {string.Join(", ", notOwned)} under codegen/generators/. Nothing was removed.");
288-
var differs = targets.Where(e => OwnedCopy.Status(root, e) != "identical").ToList();
289+
var differs = targets.Where(e => OwnedCopy.UndoStatus(root, e) != "identical").ToList();
289290
if (differs.Count > 0 && !force)
290291
return Outcome.Usage(
291292
"not removed (nothing was changed): " +
292-
string.Join("; ", differs.Select(e => $"{e.Name} {OwnedCopy.Status(root, e)}")) +
293+
string.Join("; ", differs.Select(e => $"{e.Name} {OwnedCopy.UndoStatus(root, e)}")) +
293294
". Undo drops only copies identical to the shipped generator; `dotnet meta gen --list` " +
294295
"shows how each one differs. Pass --force to discard your edits.");
295296
}
@@ -335,12 +336,6 @@ public static Outcome Undo(IReadOnlyList<string> names, string root, bool force)
335336
{
336337
var path = Path.Combine(runtimeDir, file);
337338
if (!File.Exists(path)) continue;
338-
if (!force && OwnedCopy.Compare(File.ReadAllText(path), HelperRuntime.OwnedReference(file)).Verdict
339-
!= OwnedCopy.Verdict.Identical)
340-
{
341-
lines.Add($" kept {HelperRuntime.OwnedDirectory}/{file} — it differs from the shipped one, so it is yours");
342-
continue;
343-
}
344339
File.Delete(path);
345340
lines.Add($" removed {HelperRuntime.OwnedDirectory}/{file}");
346341
runtimeRemoved = true;

‎server/csharp/MetaObjects.Cli/OwnedCopy.cs‎

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -128,10 +128,28 @@ public static Comparison Compare(string owned, string reference)
128128
return clauses.Count == 0 ? "identical" : string.Join("; ", clauses);
129129
}
130130

131-
/// <summary>Every ejectable entry whose owned copy, and the emitters eject handed over with it,
132-
/// is identical to the shipped one (<see cref="Status"/> reads <c>identical</c>).</summary>
131+
/// <summary>
132+
/// <see cref="Status"/>, plus a <c>"codegen/runtime/: …"</c> clause when the generator's output
133+
/// imports the helper runtime and the owned copy of it is not identical. Undo reads this: the
134+
/// shipped generator imports the shipped runtime, so dropping such a copy would silently stop
135+
/// the runtime edit reaching the generated code.
136+
/// </summary>
137+
public static string? UndoStatus(string root, MetaObjects.Codegen.GeneratorRegistryEntry entry)
138+
{
139+
var status = Status(root, entry);
140+
if (status is null
141+
|| !MetaObjects.Codegen.HelperRuntime.UsedBy(MetaObjects.Codegen.EjectableGenerators.ReadSource(entry.Name)!)
142+
|| RuntimeStatus(root) is not { } runtime || runtime == "identical")
143+
return status;
144+
var clause = $"{MetaObjects.Codegen.HelperRuntime.OwnedDirectory}/: {runtime}";
145+
return status == "identical" ? clause : $"{status}; {clause}";
146+
}
147+
148+
/// <summary>Every ejectable entry whose owned copy, the emitters eject handed over with it and the
149+
/// helper runtime its output imports, is identical to the shipped one (<see cref="UndoStatus"/>
150+
/// reads <c>identical</c>).</summary>
133151
public static IReadOnlyList<MetaObjects.Codegen.GeneratorRegistryEntry> IdenticalCopies(string root) =>
134-
MetaObjects.Codegen.EjectableGenerators.Entries().Where(e => Status(root, e) == "identical").ToList();
152+
MetaObjects.Codegen.EjectableGenerators.Entries().Where(e => UndoStatus(root, e) == "identical").ToList();
135153

136154
/// <summary>
137155
/// The one advisory line <c>dotnet meta gen</c> prints when owned copies identical to the shipped

‎server/java/maven-plugin/src/main/java/com/metaobjects/mojo/MetaDataEjectMojo.java‎

Lines changed: 5 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -268,7 +268,9 @@ private void undo() throws MojoExecutionException, MojoFailureException {
268268
Path codegenRoot = project.getBasedir().toPath().resolve(CODEGEN_DIR);
269269
List<OwnedStock.Copy> all;
270270
try {
271-
all = OwnedStock.find(codegenRoot, OwnedStock.classpath(getClass().getClassLoader()));
271+
all = OwnedStock.find(codegenRoot,
272+
OwnedStock.runtimeRoots(project.getBasedir().toPath(), project.getModules()),
273+
OwnedStock.classpath(getClass().getClassLoader()));
272274
} catch (IOException ex) {
273275
throw new MojoExecutionException("Could not compare the owned copies under " + codegenRoot, ex);
274276
}
@@ -988,21 +990,8 @@ private static void fold(int[] totals, List<String> differing, String name, Ejec
988990
/** The owned runtime copy of {@code simple} in this module or one of its children, or {@code null}. */
989991
private Path findOwnedRuntime(String simple) throws IOException {
990992
if (project == null || project.getBasedir() == null) return null;
991-
Path base = project.getBasedir().toPath();
992-
List<Path> roots = new ArrayList<>();
993-
roots.add(base.resolve("src/main/java"));
994-
if (project.getModules() != null) {
995-
for (String module : project.getModules()) roots.add(base.resolve(module).resolve("src/main/java"));
996-
}
997-
for (Path root : roots) {
998-
if (!Files.isDirectory(root)) continue;
999-
try (var walk = Files.walk(root)) {
1000-
for (Path p : (Iterable<Path>) walk.filter(f -> f.getFileName().toString().equals(simple + ".java"))::iterator) {
1001-
if (EjectSupport.isOwnedRuntime(Files.readString(p, StandardCharsets.UTF_8))) return p;
1002-
}
1003-
}
1004-
}
1005-
return null;
993+
return OwnedStock.findOwnedRuntime(
994+
OwnedStock.runtimeRoots(project.getBasedir().toPath(), project.getModules()), simple);
1006995
}
1007996

1008997
// ------------------------------------------------------------------------------------

‎server/java/maven-plugin/src/main/java/com/metaobjects/mojo/MetaDataGeneratorMojo.java‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -136,8 +136,10 @@ protected void executeGenerators(MetaDataLoader loader, List<Generator> generato
136136
private void noteOwnedStock() {
137137
if (project == null || project.getBasedir() == null) return;
138138
try {
139+
Path base = project.getBasedir().toPath();
139140
String line = OwnedStock.notice(OwnedStock.find(
140-
project.getBasedir().toPath().resolve(MetaDataEjectMojo.CODEGEN_DIR),
141+
base.resolve(MetaDataEjectMojo.CODEGEN_DIR),
142+
OwnedStock.runtimeRoots(base, project.getModules()),
141143
OwnedStock.classpath(getClass().getClassLoader())));
142144
if (line != null) getLog().info("MetaData Mojo > " + line);
143145
} catch (IOException | RuntimeException ignored) {

‎server/java/maven-plugin/src/main/java/com/metaobjects/mojo/OwnedStock.java‎

Lines changed: 49 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,13 @@
1818
* this plugin ships: what {@code mvn metaobjects:eject -Dundo=true} removes, and what
1919
* {@code metaobjects:generate} mentions in one advisory line.
2020
*
21-
* <p>Maven-free, like {@link EjectSupport}: it takes a directory and a resource reader. "Identical"
21+
* <p>Maven-free, like {@link EjectSupport}: it takes directories and a resource reader. "Identical"
2222
* is {@link EjectSupport#compare} over every file the eject wrote for the generator: the generator,
2323
* the classes copied with it and the page templates it reads (a file gone missing counts as
24-
* differing). The shared support and runtime copies are not part of that verdict: other owned
25-
* generators import them, so undo leaves them and names them.
24+
* differing), and the shared support and helper runtime copies it reaches. Undo never deletes the
25+
* shared copies (other owned generators import them) and names them instead; but a change to one
26+
* keeps the generator out of "identical", because the shipped generator imports the shipped
27+
* support and runtime, so the change would stop reaching the generated code.
2628
*/
2729
final class OwnedStock {
2830

@@ -76,8 +78,34 @@ static final class Copy {
7678
String stockClassname() { return entry.classname; }
7779
}
7880

79-
/** Every owned copy under {@code codegenRoot}, in catalog order; empty when there is none. */
80-
static List<Copy> find(Path codegenRoot, Reader reader) throws IOException {
81+
/** The source roots an owned helper runtime may sit under: {@code base}'s and each module's
82+
* {@code src/main/java}. */
83+
static List<Path> runtimeRoots(Path base, List<String> modules) {
84+
List<Path> roots = new ArrayList<>();
85+
roots.add(base.resolve("src/main/java"));
86+
if (modules != null) {
87+
for (String module : modules) roots.add(base.resolve(module).resolve("src/main/java"));
88+
}
89+
return roots;
90+
}
91+
92+
/** The owned runtime copy of {@code simple} under {@code roots}, recognised by its ownership
93+
* marker, or {@code null}. */
94+
static Path findOwnedRuntime(List<Path> roots, String simple) throws IOException {
95+
for (Path root : roots) {
96+
if (!Files.isDirectory(root)) continue;
97+
try (var walk = Files.walk(root)) {
98+
for (Path p : (Iterable<Path>) walk.filter(f -> f.getFileName().toString().equals(simple + ".java"))::iterator) {
99+
if (EjectSupport.isOwnedRuntime(Files.readString(p, StandardCharsets.UTF_8))) return p;
100+
}
101+
}
102+
}
103+
return null;
104+
}
105+
106+
/** Every owned copy under {@code codegenRoot}, in catalog order; empty when there is none. The
107+
* helper runtime its output imports is looked for under {@code runtimeRoots}. */
108+
static List<Copy> find(Path codegenRoot, List<Path> runtimeRoots, Reader reader) throws IOException {
81109
List<Copy> out = new ArrayList<>();
82110
if (codegenRoot == null || !Files.isDirectory(codegenRoot)) return out;
83111
for (EjectSupport.Entry e : EjectSupport.allEntries()) {
@@ -117,6 +145,22 @@ static List<Copy> find(Path codegenRoot, Reader reader) throws IOException {
117145
owned, reader.read(GeneratorRegistry.EJECT_TEMPLATE_RESOURCE_ROOT
118146
+ ref + EjectSupport.TEMPLATE_EXTENSION)));
119147
}
148+
Set<String> support = new LinkedHashSet<>();
149+
for (String source : e.sources()) support.addAll(EjectSupport.supportFor(reader.read(e.resourceOf(source))));
150+
for (String simple : support) {
151+
Path owned = MetaDataEjectMojo.findOwned(codegenRoot, "java", simple + ".java");
152+
if (owned == null) continue;
153+
fold(totals, differing, simple + ".java", EjectSupport.compare(
154+
Files.readString(owned, StandardCharsets.UTF_8),
155+
reader.read(GeneratorRegistry.EJECT_SUPPORT_RESOURCE_ROOT + simple + ".java")));
156+
}
157+
for (String simple : e.runtime) {
158+
Path owned = findOwnedRuntime(runtimeRoots, simple);
159+
if (owned == null) continue;
160+
fold(totals, differing, simple + ".java", EjectSupport.compare(
161+
Files.readString(owned, StandardCharsets.UTF_8),
162+
reader.read(GeneratorRegistry.EJECT_RUNTIME_RESOURCE_ROOT + simple + ".java")));
163+
}
120164
}
121165
out.add(new Copy(e, ownedPackage, files, differing, totals[0], totals[1],
122166
shared(codegenRoot, e, reader)));

‎server/java/maven-plugin/src/test/java/com/metaobjects/mojo/EjectUndoTest.java‎

Lines changed: 35 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,18 @@ private String logged(Log log) {
6666
return c.getAllValues().stream().map(CharSequence::toString).collect(Collectors.joining("\n"));
6767
}
6868

69+
private List<OwnedStock.Copy> findOwned() throws IOException {
70+
return OwnedStock.find(basedir.resolve("codegen"), OwnedStock.runtimeRoots(basedir, List.of()),
71+
OwnedStock.classpath(getClass().getClassLoader()));
72+
}
73+
74+
/** The helper runtime eject copied for the owned generators, under {@code src/main/java}. */
75+
private List<Path> ownedRuntime() throws IOException {
76+
try (var walk = Files.walk(basedir.resolve("src/main/java"))) {
77+
return walk.filter(p -> p.toString().endsWith(".java")).sorted().collect(Collectors.toList());
78+
}
79+
}
80+
6981
private Path ownedDto() {
7082
return basedir.resolve("codegen/src/main/java/com/acme/codegen/SpringDtoGenerator.java");
7183
}
@@ -138,19 +150,34 @@ public void anUnknownOrUnownedNameFailsTheWholeCall() throws Exception {
138150

139151
@Test
140152
public void theGenerateNoticeNamesIdenticalCopiesAndStaysSilentOtherwise() throws Exception {
141-
assertNull(OwnedStock.notice(OwnedStock.find(basedir.resolve("codegen"),
142-
OwnedStock.classpath(getClass().getClassLoader()))));
153+
assertNull(OwnedStock.notice(findOwned()));
143154
eject("dto");
144-
String line = OwnedStock.notice(OwnedStock.find(basedir.resolve("codegen"),
145-
OwnedStock.classpath(getClass().getClassLoader())));
155+
String line = OwnedStock.notice(findOwned());
146156
assertNotNull(line);
147157
assertTrue(line, line.contains("dto"));
148158
assertTrue(line, line.contains("mvn metaobjects:eject -Dundo=true"));
149159
Files.writeString(ownedDto(), Files.readString(ownedDto()) + "\n// mine\n");
150-
assertNull(OwnedStock.notice(OwnedStock.find(basedir.resolve("codegen"),
151-
OwnedStock.classpath(getClass().getClassLoader()))));
152-
assertEquals(1, OwnedStock.find(basedir.resolve("codegen"),
153-
OwnedStock.classpath(getClass().getClassLoader())).stream()
160+
assertNull(OwnedStock.notice(findOwned()));
161+
assertEquals(1, findOwned().stream()
154162
.filter(c -> c.entry.stableName.equals("dto")).count());
155163
}
164+
165+
@Test
166+
public void aChangedHelperRuntimeKeepsTheGeneratorOutOfTheNoticeAndOutOfNoNameUndo() throws Exception {
167+
eject("dto");
168+
List<Path> runtime = ownedRuntime();
169+
assertFalse("ejecting dto copies the helper runtime its output imports", runtime.isEmpty());
170+
assertNotNull(OwnedStock.notice(findOwned()));
171+
Files.writeString(runtime.get(0), Files.readString(runtime.get(0)) + "\n// mine\n");
172+
assertNull(OwnedStock.notice(findOwned()));
173+
undo(null, false);
174+
assertTrue("the generator whose runtime changed is not undone", Files.exists(ownedDto()));
175+
try {
176+
undo("dto", false);
177+
fail("expected MojoFailureException");
178+
} catch (MojoFailureException expected) {
179+
assertTrue(expected.getMessage(), expected.getMessage().contains(runtime.get(0).getFileName().toString()));
180+
}
181+
assertTrue(Files.exists(ownedDto()));
182+
}
156183
}

0 commit comments

Comments
 (0)