Skip to content

Added execute script feature, script chosen in the settings panel#83

Open
knerush wants to merge 2 commits intoTawa:mainfrom
knerush:main
Open

Added execute script feature, script chosen in the settings panel#83
knerush wants to merge 2 commits intoTawa:mainfrom
knerush:main

Conversation

@knerush
Copy link

@knerush knerush commented Dec 16, 2024

Description

This PR adds a capability to Phoenix to run an external bash script after project generation.

The main changes include:

  • Adding utility Shell.swift wrapper to call a bash script
  • The settings panel is extended to have a document picker that allows to choose a file, see CustomScriptView and CustomScriptViewModel for the UI change
  • Project generation functionality is extended with running a script if it is stored in PhoenixDocument.projectConfiguration field

References

Screenshot 2024-12-16 at 15 47 26
Generating.mov
Saving.mov

# Conflicts:
#	Modules/Contracts/Generators/PackageGeneratorContract/Sources/PackageGeneratorContract/PackageGeneratorContract.swift
#	Modules/Generators/PackageGenerator/Sources/PackageGenerator/PackageGenerator.swift
#	Modules/Generators/PackageGenerator/Sources/PackageGenerator/Utils/Shell.swift

Saving custom script path in project configuration json

# Conflicts:
#	Phoenix.xcodeproj/project.pbxproj

Passing rootUrl to contentView, to be able to calculate relative path for script

UI tweaks
Nonnus
Nonnus previously approved these changes Dec 17, 2024
Copy link
Collaborator

@Nonnus Nonnus left a comment

Choose a reason for hiding this comment

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

This looks great, awesome job!
IMHO I feel that it might be better to clearly name the Script a Shell Script considering that is the only supported option (unlike eg: swift script) both in code (eg: CustomScriptViewModel -> CustomShellScriptViewModel) and on UI (Custom Script -> Custom Shell Script)

Copy link
Owner

@Tawa Tawa left a comment

Choose a reason for hiding this comment

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

Thanks a lot for implementing this feature, and for opening this PR, sorry it took me so long to take a look at it.

I requested some minor changes, but in general the PR looks good. 👍

try container.encode(swiftVersion, forKey: .swiftVersion)
try container.encodeIfPresent(defaultOrganizationIdentifier, forKey: .defaultOrganizationIdentifier)
try container.encode(platforms, forKey: .platforms)
try container.encode(customScriptPath, forKey: .customScriptPath)
Copy link
Owner

Choose a reason for hiding this comment

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

I believe this should be encodeIfPresent

guard fileManager.fileExists(atPath: url.path) else { return }

let shell = Shell(verbose: true)
_ = try shell.executeScript(at: url.absoluteURL.absoluteString)
Copy link
Owner

Choose a reason for hiding this comment

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

The executeScript function already is annotated with @discardableResult, so you can remove the _ = .

}

public func runExternalScriptIfNeeded(at url: URL) throws {
guard fileManager.fileExists(atPath: url.path) else { return }
Copy link
Owner

Choose a reason for hiding this comment

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

I'm not sure I like that this function does not notify the user when the script file does not exist. Maybe we should throw an error here and show it in the UI for the user to be aware of the issue? Otherwise things might be broken and the user might not know about it.

Copy link
Owner

Choose a reason for hiding this comment

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

I just saw that the shell.executeScript function already throws an error if it fails to execute the file, so let's remove this guard here and rely on the Shell object to communicate that error.

let relativePath = relativeComponents.joined(separator: "/")
return relativePath
} else {
print("Script file should be under Modules folder")
Copy link
Owner

Choose a reason for hiding this comment

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

I feel that this should be communicated differently to the user as well, but it's not a blocker to merge this PR.

Comment on lines +29 to +33
output.split(separator: "\n").forEach { line in
Task {
await Console.print(level: 1, .none, String(line))
}
}
Copy link
Owner

Choose a reason for hiding this comment

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

Suggested change
output.split(separator: "\n").forEach { line in
Task {
await Console.print(level: 1, .none, String(line))
}
}
Task {
output.split(separator: "\n").forEach { line in
await Console.print(level: 1, .none, String(line))
}
}

Here it's best to create 1 task for all lines instead of 1 task per line.

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.

3 participants