-
Notifications
You must be signed in to change notification settings - Fork 517
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
fix(build-tools): restore some tsc dep checking #20971
fix(build-tools): restore some tsc dep checking #20971
Conversation
fluid-build-tasks-tsc policy handler was searching for command line of exactly "tsc" which has been mostly abandoned since Node16+ESM pattern. Create a build database that understands basic build tasks, their output, and their CJS/ESM slant. From that build approximate predecessor lists to replace "tsc" assumption. The predecessor list is approximate as a package with multiple ESM scripts may produce the required output from any one of those scripts. Until tsc incremental is run and generates required inputs precise dependencies cannot be established. This change does not add a post build policy check for such precise dependencies. So policy addressee must choose the correct predecessor from a list presented. Refactor logic from entrypoints to be used by database to reason able package exports for `generate entrypoints` scripts. Apply policy corrections. One of note is api-markdown-documenter that builds ESM from script named tsc. Future: fluid-build-tasks-eslint should be moved off of getDefaultTscTaskDependencies that looks for "tsc".
fc63ea3
to
893747d
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No objection to these changes, but I wonder if they're needed. What do they protect us against? Would it be easier to use fluid-build's task graph for some of the logic? Seems like you're producing the equivalent here so maybe fluid-build itself could be more involved in finding problems?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's a little unclear to me when I would use the fluidBuildDatabase vs. the task graph from fluid-build.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Task graph is defined before build and very quick. fluidBuildDatabase is imprecise until after the build completes as it only understands output from commands and not the inputs. So, it can say that one of the tasks in package A is needed by B, but not precisely which of those tasks. After a complete build, tsbuildinfo will have complete inputs required including and ESM/CJS cross over. From that input list we can add a [post-build] policy check on task config that is 100% accurate.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
After a complete build, tsbuildinfo will have complete inputs required including and ESM/CJS cross over. From that input list we can add a [post-build] policy check on task config that is 100% accurate.
And that input list is what fluidBuildDatabase ends up with, I suppose? Might be worth putting copying this explanation (and a bit more? It's still not 100% clear to me 😅 ) to the class tsdocs.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I mispoke a bit. FluidBuildDatabase doesn't currently end up with that complete information even after build and probably won't. FluidBuildDatabase only has the outputs of scripts. I am adding a comment noting that.
After build we can read the inputs from tsbuildinfo and search FluidBuildDatabase's output knowledge to find source scripts. I don't think we need to store the input into the database, too.
// Generic build:test script should be replaced by :esm or :cjs specific versions. | ||
// "tsc" would be nice to eliminate from here, but plenty of packages still focus | ||
// on CommonJS. | ||
"build:test": ["typetests:gen", "tsc", "api-extractor:commonjs", "api-extractor:esnext"], |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This seems like an odd combination. The test script name isn't specific, so if the package builds both commonjs and esm tests, shouldn't both be built by running build:test?
"build:test": ["typetests:gen", "tsc", "api-extractor:commonjs", "api-extractor:esnext"], | |
"build:test": ["build:test:cjs", "build:test:esnext"], |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If package builds both esm and cjs tests, then those individual test build scripts should have their appropriate dependencies and this isn't needed. This config should be removed, but first the config needs moved to the places that still build tests from build:test. I was doing the minimal changes that are happy with current and new policy and avoiding looking at individual packages.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
and this isn't needed
I'd say maybe we still want it? We have the single build/compile that covers commonjs and build:esnext, so having the same pattern for tests makes sense to me. That said, for another PR.
"build:tsc": [ | ||
"^tsc" | ||
"tsc": [ | ||
"^build:esnext" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why not rename the script to build:esnext instead of tsc?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Because I was not touching scripts and potentially breaking workflows I didn't know about. Leave that for later/others. tools is outside of the fits a standard pattern world and that is not what this task is about.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't see any references to build:tsc in the repo other than the npm script above and the task entry here... not sure what either of them is even accomplishing since nothing else triggers them through fluid build
. If we change the task entry I think we want to remove the npm script too. @Josmithr might be able to confirm?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We don't need to remove the script entry even if there was a tie in from something else to build:tsc
. build:tsc
runs tsc
which the fluid build system automatically recognizes as a dependency. So, any build:tsc
task in the system has these manually configured dependencies.
If build:tsc
script is excessive (unused), then it could be removed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, I think it is unused. It's defined and has a task entry here, but no other task/script ever runs it as far as I can tell. Can be removed separately though.
if (typeof value === "string") { | ||
if (entry === "types") { | ||
for (const [key, apiTagLevel] of mapQueryPathToApiTagLevel.entries()) { | ||
// eslint-disable-next-line max-depth |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: extracting the body of either for-loop into its own function might make this disable unecessary.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It would. I felt it was better to read the contents of the function here rather than extract for this aggressive rule (my opinion) and try to name/explain the function and handle the return. ?
We can also mitigate rule violation by making line 47 "if" negative and "continue"ing which would bring this all up one level, but continue can be harder to follow.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not particularly concerned about the line length rule itself, more commenting on the fact that there is a lot of logical nesting going on here. I'm wondering if it might make it a bit more readable to pull out pieces and document what they do in isolation. I'm honestly not sure if it would, but there is a fair amount going on here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I went in to see if comments would help clear things up and that lead me to the things that aren't handled as we don't author outside of a pattern. I started editing to handle those and found a way to move a little out and I think it is better now.
build-tools/packages/build-cli/src/library/repoPolicyCheck/fluidBuildDatabase.ts
Show resolved
Hide resolved
"fluidBuild": { | ||
"tasks": { | ||
"build:test": [ | ||
"^tsc", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For my/reviewer reference, packages have build:test
depend on ^tsc
+ ^api-extractor:commonjs
when they are commonjs and on ^build:esnext
+ ^api-extractor:esnext
when they're esm.
Tangent: we should probably make this package esm, but not in this PR.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yep. And we prefer to have build:test:cjs|esm
for clarity. build:test
can exist to run either or both of those but doesn't need dependencies itself. We just need to make sure there are no more build:test
that do things directly.
Re Tangent: I knew about this. For now, I thought it was good to have something that runs CommonJS. There are some tasks to make sure we have correct coverage on ESM / CJS.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Haven't looked at the fluidBuildDatabase or the policy nor fluidBuildTasks.ts; the rest looks ok except for potentially one comment left independently above.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A few more comments, nothing critical. I can follow the general idea of what's happening and it seems fine, I just can't quite follow the details in my head.
I'll say that several things in these packages (like hasTaskDependency
) would benefit from unit tests. I'll leave it up to you if it makes sense to add some here or capture work items for the backlog.
build-tools/packages/build-cli/src/library/repoPolicyCheck/fluidBuildTasks.ts
Outdated
Show resolved
Hide resolved
build-tools/packages/build-cli/src/library/repoPolicyCheck/fluidBuildTasks.ts
Outdated
Show resolved
Hide resolved
It's mostly lack of familiarity with a good chunk of the code this is touching or calling. Having to go several function calls deep in code that's new to me to actually understand what something in your changes is doing, a few times per file or per function, starts overwhelming my capacity to keep a solid mental model of the system as a whole. I'd say docs on the helper functions (both newly added ones and pre-existing ones you used) would help, that'd be a good place for further explanation so I could either trust the functions and not have to go through them line by line to understand them, or if I decide to still go through them, at least have some baseline of what I'm expecting the code there to be doing. |
pkg: Package, | ||
commandLine: string, | ||
): { files: AbsoluteFilePath[]; type: ModuleType | undefined } | undefined { | ||
if (!commandLine.startsWith("flub generate entrypoints")) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this check because this function runs as a part of flub generate entrypoints
? This feels fragile to me. At the very least, it seems like we should extract this into a constant with some docs.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is not a part of flub generate entrypoints
. This is helper to derive what output a flub
command would generate. generate entrypoints
is the only one I know we need.
It would be better if *Task classes or some part of them could be leveraged. I checked that out briefly but there are too many dependencies and complications.
Same for the fluid commands. Maybe if I understood oclif better there would be a better refactor to reuse the command processing. There is also a complication with code organization and cyclical import dependencies that make relocating Commands hard. Best I could do was extract some common logic. Having said that I might actually be able to clear up the cycle and do something better.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Relocated command and shared more logic
- tighten up typing with readonly - some refactoring for readability - revise isTypeOnly handling to support more exports structuring patterns - add unit tests for packageExports.ts
to library under new commands folder for logic reuse - promote base.ts to library to prevent import cycle - move rootPathFlag from flags.ts
to get outputs
per [ts-node issue783](TypeStrong/ts-node#783)
as only use is within library
build-tools/packages/build-cli/src/library/commands/generateEntrypoints.ts
Show resolved
Hide resolved
build-tools/packages/build-cli/src/library/commands/generateEntrypoints.ts
Outdated
Show resolved
Hide resolved
const rootPathFlag = Flags.custom({ | ||
description: "Root directory of the Fluid repo (default: env _FLUID_ROOT_).", | ||
env: "_FLUID_ROOT_", | ||
hidden: true, | ||
}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I was already writing that this felt better with the rest of the reusable flags but then noticed it's part of the BaseCommand
that every other one extends and it's not referenced anywhere else, so I think moving it here makes sense.
build-tools/packages/build-cli/src/library/commands/constants.ts
Outdated
Show resolved
Hide resolved
/** | ||
* Generates type declarations files for Fluid Framework APIs to support API levels (/alpha, /beta. etc.). | ||
*/ | ||
export class GenerateEntrypointsCommand extends BaseCommand< |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I find it weird to move the whole command (and BaseCommand) to library/ instead of just the necessary helper functions (cursory inspection seemed to suggest we can move just some functions but maybe I missed something). Wouldn't block on this but seems worth revisiting. If it's absolutely necessary, so be it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Moving just some helpers is what I originally was doing. The real thing that we want to share is the argument processing up to the point of knowing what files are generated. We can't share the argument processing as it is too opaque (at least I couldn't find the way to extract that). So, the next best is to have very similar and somewhat tied together processing in one file and for that I needed the flags
out of Command. It didn't seem readable to separate flags
from the Command. So, now they can be in one place and we can access two parts as needed.
Another option was to get repoPolicyCheck out of library, but that appeared more complicated.
actual: Map<TKeys, TValues>, | ||
expected: Map<TKeys, TValues>, | ||
): void { | ||
assert.hasAllKeys(actual, [...expected.keys()]); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What scenario does assert.hasAllKeys
cover that isn't handled by the loop below?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
assert.hasAllKeys
confirms that keys are identical - those keys and only those keys (I think the name could be better). The below would allow extra keys.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You could simplify all of this as something like
assert.deepEquals([...actual.entries()], [...expected.entries()]);
I don't know if the error messages would be as useful though.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I liked the output of hasAllKeys. And then there is some nicety to being able to break/debug on single value. Thanks though :)
build-tools/packages/build-cli/src/library/repoPolicyCheck/fluidBuildDatabase.ts
Outdated
Show resolved
Hide resolved
build-tools/packages/build-cli/src/library/repoPolicyCheck/fluidBuildTasks.ts
Show resolved
Hide resolved
build-tools/packages/build-cli/src/library/repoPolicyCheck/fluidBuildTasks.ts
Outdated
Show resolved
Hide resolved
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
My overall understanding of how fluid-build functions is pretty limited. But given that, these changes seem reasonable to me.
I would really love to see us invest in moving away from fluid-build more generally, though. The logic involved keeps expanding, and the set of people who know what's going on is very small.
fix(build-tools): restore some tsc dep checking
fluid-build-tasks-tsc policy handler was searching for command line of exactly "tsc" which has been mostly abandoned since Node16+ESM pattern.
Create a build database that understands basic build tasks, their output, and their CJS/ESM slant. From that, build approximate predecessor lists to replace "tsc" assumption.
The predecessor list is approximate as a package with multiple ESM (or CJS) build scripts may produce the required output from any one of those scripts. Until tsc incremental is run and generates required inputs, precise dependencies cannot be established. This change does not add a post build policy check for such precise dependencies. So, policy addressee must choose the correct predecessor from a list presented. An example of choices presented:
Relocate GenerateEntrypointsCommand to library/commands (new folder) so database can use shared logic to get output files. Package exports logic was further broken out and placed under test. Supporting changes:
Apply policy corrections. One of note is api-markdown-documenter that builds ESM from script named tsc.
Follow-up: fluid-build-tasks-eslint should be moved off of getDefaultTscTaskDependencies that looks for "tsc".