Skip to content

Conversation

airtonix
Copy link

@airtonix airtonix commented Aug 23, 2025

fixes #1228

Provides a recursive string replacer that takes a map of things to replace.

Initially created to solve moonrepo problems... but highly likely other things with commands that allow templating would need this treatment too.

const processor = createCommandProcessor({ '$projectRoot': '/path/to/project' });
const command = processor('echo $projectRoot');

console.log(command);
// 'echo /path/to/project'

processor(['npx', 'tsx', '$projectRoot/server/worker.js', '--', '--force']);
// ['npx', 'tsx', '/path/to/project/server/worker.js', '--', '--force']

@webpro
Copy link
Member

webpro commented Aug 23, 2025

Thanks for the PR! Just thinking out loud here (sorry if I'm ahead of myself here), why not [].join(" ") e.g.:

options.getInputsFromScripts(['program', 'command '--', '--flag'].join(" "))

@airtonix
Copy link
Author

airtonix commented Aug 25, 2025

Thanks for the PR! Just thinking out loud here (sorry if I'm ahead of myself here), why not [].join(" ") e.g.:

options.getInputsFromScripts(['program', 'command '--', '--flag'].join(" "))

You mean instead of recursively diving into what ever it is that's in the command?

I guess I just assumed that it would be better to leave it in the original form.

Is it knip that ends up executing the command? or does it just look at it and say "huh... that's interesting?"

Perhaps it's worth understanding what knip does with these commands that are returned from resolveConfig?

https://knip.dev/guides/writing-a-plugin#6-resolveconfig

Should I implement resolveConfig?
You should implement resolveConfig if any of these are true:

  1. The config file contains one or more options that represent entry points
  2. The config file references dependencies by strings (not import statements)

So something worth noting about moonrepo is that a task can describe useful work with either script or command.

pkgs/dockerfiles/moon.yml

# yaml-language-server: $schema=./../../.moon/cache/schemas/project.json

fileGroups:
  env:
    - "${workspaceRoot}/.env" 
  dockerfiles:
    - "core/Dockerfile"
    - "base/Dockerfile"

tasks:
  build:
    options:
      runInCI: false
      runFromWorkspaceRoot: true
      outputStyle: stream
    script: |
      #!/bin/bash
      set -euo pipefail

      mise run docker:build \
        --dir "$(pwd)" \
        --secret type=env,id=MISE_GITHUB_TOKEN,env=MISE_GITHUB_TOKEN \
        --file "pkgs/dockerfiles/Dockerfile" \
        --image ghcr.io/me/mine \
        --version "$(jq -r .version pkgs/dockerfiles/package.json)"
  1. a script task is run much like justfile handles tasks: they're dumped to some temporary place, chmod +x ./thefile and run.
  • you can't do moon mine:build -- --moreDockerBuildFlags (only command based tasks can do that)
  1. a command task, is run much like python or nodejs allow for running shell cmds, in string or arg form.
  • in Arg form, you can add more args and have them interpreted by the underlying shell properly.
  1. A task can describe things that end up being used to generate a cache key to determine if the task should be skipped next time it's called or not.
  • These come from the fileGroups section here in the file or further up the file tree in the root .moon/* folder.

Point 6 says:

The plugin should return the entry paths and dependencies referenced in this object.

But this is not really what the original moonrepo plugin was doing to begin with.

If we want to explore doing this properly for the moonrepo plugin we should probably answer these questions of how important that information is.

@airtonix
Copy link
Author

side note: if mise allowed for easier merging of task files into a better command structure from throughout a monorepo I'd probably be using mise instead of moonrepo all together tbh.

@webpro
Copy link
Member

webpro commented Aug 25, 2025

Is it knip that ends up executing the command? or does it just look at it and say "huh... that's interesting?"

It's all pretty much just static analysis here (especially when it comes to YAML/JSON like here in this plugin). Knip definitely does not execute anything. The only "dynamic" thing Knip does is load/import JS/TS config files like eslint.config.js or lint-staged.ts yet only to statically parse the exported value of such files.

Perhaps it's worth understanding what knip does with these commands that are returned from resolveConfig?

Knip plugins return so-called "inputs", as created with the toEntry and toDependency functions. Entry files are added by core to the files/exports analysis (to handle imports/exports), and dependencies might eventually cause "unlisted" or "unused" dependencies.

To extract entry files and dependencies from scripts, Knip provides the getInputsFromScripts helper. This helper takes care of creating inputs, so we can just add its return value to what resolveConfig() returns.

Does that help? AMA :)

@airtonix
Copy link
Author

Which is why you're suggesting to simplify the problem by squashing the array to one string.

Seems good.

Do we still want this generic template replacer thing or you want me to just turf it?

@webpro
Copy link
Member

webpro commented Sep 2, 2025

Please remove createCommandProcessor from the pull request and I'm happy to review again.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

🧩 moonrepo plugin assumes commands are strings

2 participants