Skip to content

Add run command#36

Merged
mtj0928 merged 14 commits intomtj0928:mainfrom
coffmark:feature/run-command
May 29, 2025
Merged

Add run command#36
mtj0928 merged 14 commits intomtj0928:mainfrom
coffmark:feature/run-command

Conversation

@coffmark
Copy link
Copy Markdown
Contributor

@coffmark coffmark commented May 13, 2025

Motivation

When nestfile.yaml was updated, version mismatches occurred when directly executing local binaries.

Therefore, I was using Git Hooks to run $ nest bootstrap to install the correct version of nestfile.yaml.

As an alternative to directly executing binaries, we can now use $ nest run owner/repository to execute binaries.

Describe changes

I newly implemented RunCommand.swift.

Run command accepts the following arguments and options.

$ nest help run

OVERVIEW: Run executable file on a given nestfile. If not found, it will
attempt to install.

USAGE: nest run [--verbose] [--no-install] [--nestfile-path <nestfile-path>] <arguments> ...

ARGUMENTS:
  <arguments>

OPTIONS:
  -v, --verbose
  --no-install            Will not perform installation.
  --nestfile-path <nestfile-path>
                          A nestfile written in yaml. (default: nestfile.yaml)
  -h, --help              Show help information.

I used NestInfoController for manipulating NestInfo and NestfileController for manipulating Nestfile.

Operation check

I used realm/SwiftLint as an example.

  1. When realm/SwiftLint specified in nestfile.yaml is installed.
$ .nest/artifacts/realm_SwiftLint_github.com_https/0.55.0/SwiftLintBinary-macos/swiftlint lint Sources/NestKit
Linting Swift files at paths Sources/NestKit
// ...
Done linting! Found 44 violations, 0 serious in 34 files.
  1. When realm/SwiftLint specified in nestfile.yaml is not installed
📦 Found an artifact bundle, SwiftLintBinary-macos.artifactbundle.zip, for SwiftLint.
🌐 Downloading the artifact bundle of SwiftLint...
✅ Success to download the artifact bundle of SwiftLint.
🪺 Success to install swiftlint version 0.55.0.
$ .nest/artifacts/realm_SwiftLint_github.com_https/0.55.0/SwiftLintBinary-macos/swiftlint lint Sources/NestKit
Linting Swift files at paths Sources/NestKit
// ...
Done linting! Found 44 violations, 0 serious in 34 files.
  1. When realm/SwiftLint specified in nestfile.yaml is not installed and --no-install option is enabled

$ nest run --no-install realm/SwiftLint lint Sources/NestKit

Failed to find binary path, likely because it's not installed. Please try without the --no-install option or run the bootstrap command.

Issue ticket number and link

Checklist before requesting a review

  • I have performed a self-review of my code
  • I have added tests.

@coffmark coffmark force-pushed the feature/run-command branch 4 times, most recently from 371f846 to 198c014 Compare May 15, 2025 21:38
@coffmark coffmark force-pushed the feature/run-command branch from 198c014 to 849bd8b Compare May 15, 2025 21:41
@coffmark coffmark changed the title [WIP] Add run command Add run command May 15, 2025
@coffmark coffmark marked this pull request as ready for review May 15, 2025 21:43
}

/// Get the version that matches the `owner/repo`
public func fetchTarget(reference: String, nestfile: Nestfile) -> Nestfile.Target? {
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Naming] fetch sounds that the function get the target from a remote server.

How about this interface?

public func target(matchingTo reference: String, in nestfile: Nestfile) -> Nestfile.Target? 

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And please write DocC that explains the reference can accept only owner/repo format.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks!
I've made the corrections here. (297dda5)

return nestfile.targets
.first { target in
guard case let .repository(repository) = target,
repository.reference == reference || GitURL.parse(string: repository.reference)?.referenceName == reference
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is not this enough?

Suggested change
repository.reference == reference || GitURL.parse(string: repository.reference)?.referenceName == reference
GitURL.parse(string: repository.reference)?.referenceName == reference

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks!
I've made the corrections here. (5571860)

Comment on lines +5 to +11
/// `owner/repo` format
public var referenceName: String? {
guard pathComponents.count >= 3 else { return nil }
let owner = pathComponents[1]
let repo = pathComponents[2].replacingOccurrences(of: ".\(pathExtension)", with: "")
return "\(owner)/\(repo)"
}
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Question] I'm not sure whether the logic always correct or not.
For example, is a URL like https://for.com/bar/{owner}/{repo} impossible?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is a URL like https://for.com/bar/{owner}/{repo} impossible?

In this case, bar/owner is output as a reference.
I added comments and tests (174375c)

}

/// Get NestInfo.Command from `owner/repository`
public func fetchCommand(reference: String, version: String) -> NestInfo.Command? {
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ditto (naming)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks!
I've made the corrections here. (cbbe015)

Comment on lines +26 to +35
let command = $0.value
.first {
switch $0.manufacturer {
case let .artifactBundle(sourceInfo):
return sourceInfo.repository?.reference.referenceName == reference
case let .localBuild(repository):
return repository.reference.referenceName == reference
}
}
return command != nil
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This may be straightforward.

Suggested change
let command = $0.value
.first {
switch $0.manufacturer {
case let .artifactBundle(sourceInfo):
return sourceInfo.repository?.reference.referenceName == reference
case let .localBuild(repository):
return repository.reference.referenceName == reference
}
}
return command != nil
$0.value
.contains {
switch $0.manufacturer {
case let .artifactBundle(sourceInfo):
return sourceInfo.repository?.reference.referenceName == reference
case let .localBuild(repository):
return repository.reference.referenceName == reference
}
}

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks!
I've made the corrections here. (3b4f465)

Comment thread Sources/nest/Commands/RunCommand.swift Outdated
@Flag(help: "Will not perform installation.")
var noInstall: Bool = false

@Option(help: "A nestfile written in yaml.")
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That nestfile is written in yaml is obvious.

Suggested change
@Option(help: "A nestfile written in yaml.")
@Option(help: "A path to nestfile")

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks!
I've made the corrections here. (6f71256)

import NestCLI
import Logging

struct RunCommand: AsyncParsableCommand {
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nest module should be thin as much as possible.
So could you try to move the logic to other modules and reduce the logic in this module?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks!
I tried to corrected here. (1ee1a5f, 8306591)

Comment on lines +6 to +16
guard !isSkip else {
self = .skip
return
}
guard let expectedChecksum else {
self = .printActual { checksum in
logger.info("ℹ️ The checksum is \(checksum). Please add it to the nestfile to verify the downloaded file.")
}
return
}
self = .needsCheck(expected: expectedChecksum)
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think guard statement is for error case.

guard foo else {
    // error case
}
// happy path

In this function, skip and printActual cases are not error cases.
So, could you use if statements instead?

Copy link
Copy Markdown
Contributor Author

@coffmark coffmark May 20, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks!
I've made the corrections here. (bf4e6e2)

Comment thread Sources/nest/Commands/RunCommand.swift Outdated
artifactBundleManager: ArtifactBundleManager,
logger: Logger
) async throws -> String? {
guard let binaryRelativePath = nestInfoController.fetchCommand(reference: reference, version: version)?.binaryPath
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use if statement instead of guard statement.

Copy link
Copy Markdown
Contributor Author

@coffmark coffmark May 20, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks!
I fixed it together here. (bf4e6e2)

@coffmark coffmark changed the title Add run command [WIP] Add run command May 18, 2025
@coffmark
Copy link
Copy Markdown
Contributor Author

Thanks for your review!
I will contact you when the corrections are complete.

coffmark added a commit to coffmark/nest that referenced this pull request May 20, 2025
coffmark added a commit to coffmark/nest that referenced this pull request May 20, 2025
@coffmark coffmark force-pushed the feature/run-command branch from 4bddde4 to d800cf2 Compare May 20, 2025 21:42
@coffmark coffmark force-pushed the feature/run-command branch from d800cf2 to bf4e6e2 Compare May 20, 2025 21:44
Comment on lines +30 to +42
/// Returns a relative path. If not, it will attempt to install.
public func resolveBinaryRelativePath(
noInstall: Bool,
reference: String,
version: String,
target: Nestfile.Target,
gitURL: GitURL,
gitVersion: GitVersion,
nestInfoController: NestInfoController,
executableBinaryPreparer: ExecutableBinaryPreparer,
artifactBundleManager: ArtifactBundleManager,
logger: Logger
) async throws -> String? {
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ideally, I would write tests, but there are no tests for the dependent component (ExecutableBinaryPreparer), so I decided not to write tests for the resolveBinaryRelativePath method.

(If it is required, I will write tests together with ExecutableBinaryPreparer!)

Comment on lines +61 to +74
public var assetName: String? {
switch self {
case .repository(let repository): repository.assetName
case .zip, .deprecatedZIP: nil
}
}

public var checksum: String? {
switch self {
case .repository(let repository): repository.checksum
case .zip(let zipURL): zipURL.checksum
case .deprecatedZIP: nil
}
}
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I moved properties used by bootstrap and run commands.

@coffmark coffmark changed the title [WIP] Add run command Add run command May 21, 2025
@coffmark coffmark requested a review from mtj0928 May 21, 2025 22:05
Copy link
Copy Markdown
Owner

@mtj0928 mtj0928 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, except for the bug mentioned in a comment.
And, could you update README.md to explain the added command?

Comment on lines +78 to +82
_ = try await NestProcessExecutor(logger: logger, logLevel: .info)
.execute(
command: "\(nestDirectory.rootDirectory.relativePath)\(binaryRelativePath)",
executor.subcommands
)
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I noticed a bug that a user cannot input any texts to the command even if the command requires inputs because NestProcessExecutor does't support it.

It's OK to merge this PR without fixing the issue and fix it in another PR.
Of course, it's also welcome to fix it in this PR!

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would appreciate it if you could merge this PR as the differences are getting bigger.

As for the bugs, I will fix it and submit a PR. (If you need to create an Issue, I will create it.)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@mtj0928
I tried writing a sample CLI tool to receive standard input, but could not reproduce it. (https://github.com/coffmark/InputText)

v0.0.1 -> only use readline method. (source code)
v0.0.2 -> use readline method with swift-argument-parser (source code)

Would it be possible for you to share the name of the CLI tool (if it is publicly available) or the steps to reproduce it?

Screenshot.2025-05-30.8.01.20.mov

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, sorry.
I might misunderstood something.
Thank you for the investigation!
For now, it's fine to keep the current code.

Copy link
Copy Markdown
Owner

@mtj0928 mtj0928 May 31, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FYI: I've noticed that the input mechanism of xcodes's select doesn't work. #38

@coffmark
Copy link
Copy Markdown
Contributor Author

LGTM, except for the bug mentioned in a comment. And, could you update README.md to explain the added command?

Thanks for your review!
I have corrected it here. (ce62f3f)

@coffmark coffmark requested a review from mtj0928 May 27, 2025 23:07
Copy link
Copy Markdown
Owner

@mtj0928 mtj0928 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Basically, LGTM!
Let me modify the code a bit after merging this PR, for very personal preferences.

@mtj0928 mtj0928 merged commit 6c4d0f0 into mtj0928:main May 29, 2025
1 check passed
@coffmark coffmark deleted the feature/run-command branch May 29, 2025 22:31
@coffmark
Copy link
Copy Markdown
Contributor Author

Thank you for merging!

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.

2 participants