;;; sfdx --- Emacs wrapper for basic sfdx cli commands ;;; Commentary: ;;; Code: (require 'transient) (defvar sfdx-create-css) (setq-default sfdx-create-css t) (defun sfdx/next-component-file () "Find next file with the same name, but different file extension." (interactive) (let ( (current-file-name (file-name-sans-extension (buffer-file-name))) (current-ext (file-name-extension (buffer-file-name))) ) (when (string= current-ext "js") (find-file (concat current-file-name ".html"))) (when (string= current-ext "html") (if (file-exists-p (concat current-file-name ".css")) (find-file (concat current-file-name ".css")) (if (file-exists-p (concat current-file-name ".scss")) (find-file (concat current-file-name ".scss")) (if (and sfdx-create-css (yes-or-no-p "Do you want to create a CSS file?")) (find-file (concat current-file-name ".css")) (setq-local sfdx-create-css nil) (find-file (concat current-file-name ".js")))))) (when (string= current-ext "css") (find-file (concat current-file-name ".js"))) (when (string= current-ext "scss") (find-file (concat current-file-name ".js"))) )) (defun sfdx--goto-project (project-path) "Internal function to load the PROJECT-PATH in current window." ;; DEBUG - this isn't working to auto-open the folder. ;; (find-file project-path) (message project-path)) (defun sfdx/exec-process (cmd name &optional comint) "Execute CMD as a process in a buffer NAME, optionally passing COMINT as non-nil to put buffer in `comint-mode'." (let ((compilation-buffer-name-function (lambda (mode) (format "*%s*" name)))) (message (concat "Running " cmd)) (compile cmd comint))) (defun sfdx/create-project () "Create a new 'standard' SFDX project." (interactive) (let ( (process "sfdx-create-project") (project-name (read-string "Project Name: ")) (project-dir (read-directory-name "Directory: " "~/Projects")) ) (async-start-process process "sh" `(lambda (result) (sfdx--goto-project (concat (expand-file-name ',project-dir) ',project-name))) "-c" (concat "sfdx force:project:create --projectname " project-name " --outputdir " (expand-file-name project-dir) " --template standard")) )) (defun sfdx/create-component () "Create a new Lightning Web Component." (interactive) (if (locate-dominating-file buffer-file-name "force-app") (let ((process "sfdx-create-component") (output-path (concat (locate-dominating-file buffer-file-name "force-app") "force-app/main/default/lwc/")) (comp-name (read-string "Component Name: ")) ) (async-start-process process "sh" (lambda (result) (message "Component Created")) "-c" (concat "sfdx force:lightning:component:create --type lwc --componentname " comp-name " --outputdir " output-path)) ) (message "You must be in an SFDX project to run that command!"))) (defun sfdx/fetch-component () "Fetch a Lightning Web Component from Org." (interactive) (if (locate-dominating-file buffer-file-name "force-app") (let ((process "sfdx-fetch-component") (cd-dir (concat (locate-dominating-file buffer-file-name "force-app") "force-app/main/default/lwc/")) (comp-name (read-string "Component Name: ")) ) (sfdx/exec-process (format "sh -c \"cd %s; sfdx force:source:retrieve -m LightningComponentBundle:%s\"" cd-dir comp-name) "sfdx:retrieve_component" t) ;; (async-start-process process "sh" (lambda (result) (message (concat "Component Retrieved: \"" comp-name "\""))) "-c" (concat "sfdx force:source:retrieve -m LightningComponentBundle:" comp-name)) ) (message "You must be in an SFDX project to run that command!"))) (defun sfdx--deploy (component comp-name) "Internal function to deploy COMP-NAME asyncronously or whole project if COMPONENT is nil after validations." (let ((process "sfdx-deploy") (buffer "*sfdx-output*") (cd-dir (expand-file-name (locate-dominating-file buffer-file-name "force-app"))) (output-path (concat (locate-dominating-file buffer-file-name "force-app") "force-app/main/default"))) (if component (sfdx/exec-process (format "sh -c \"cd %s; sfdx force:source:deploy --sourcepath ./force-app/main/default/lwc/%s --loglevel fatal\"" cd-dir comp-name) "sfdx:deploy_component" t) (sfdx/exec-process (format "sh -c \"cd %s; sfdx force:source:deploy --sourcepath ./force-app/main/default/ --loglevel fatal\"" cd-dir) "sfdx:deploy_project" t)))) (defun sfdx--retrieve (component comp-name) "Internal function to retrieve COMP-NAME asyncronously or whole project if COMPONENT is nil after validations." (let ((process "sfdx-deploy") (buffer "*sfdx-output*") (cd-dir (expand-file-name (locate-dominating-file buffer-file-name "force-app"))) (output-path (concat (locate-dominating-file buffer-file-name "force-app") "force-app/main/default"))) (if component (sfdx/exec-process (format "sh -c \"cd %s; sfdx force:source:retrieve --sourcepath ./force-app/main/default/lwc/%s --loglevel fatal\"" cd-dir comp-name) "sfdx:retrieve_component" t) (progn (if (yes-or-no-p "Are you sure? This will completely overwrite any local changes! ") (sfdx/exec-process (format "sh -c \"cd %s; sfdx force:source:retrieve --sourcepath ./force-app/main/default/ --loglevel fatal\"" cd-dir) "sfdx:retrieve_project" t) (message "Cancelled") ))))) (defun sfdx/deploy-component-or-project () "Deploy the current component or project to target." (interactive) (let ((current-folder (file-name-nondirectory (directory-file-name (file-name-directory (buffer-file-name)))))) (if (locate-dominating-file buffer-file-name "lwc") (prog1 ;; Possibly in a component folder, but lets makes sure its not just the LWC folder. (if (string= current-folder "lwc") (prog1 ;; Not in a component, deploy project. ;; (message "Deploying Project...") (sfdx--deploy nil current-folder)) ;; In a component, deploy component. ;; (message "Deploying Component...") (sfdx--deploy t current-folder))) (prog1 ;; Are we in a project? (if (locate-dominating-file buffer-file-name "force-app") (prog1 ;; In project, deploy project. ;; (message "Deploying Project...") (sfdx--deploy nil current-folder)) (prog1 ;; Not in an SFDX project. (message "You are not in a component folder or an SFDX project!")) ) ) ) ) ) (defun sfdx/retrieve-component () "Retrieve the source for the current component (destructively overwrites)." (interactive) (let ((current-folder (file-name-nondirectory (directory-file-name (file-name-directory (buffer-file-name)))))) (if (locate-dominating-file buffer-file-name "lwc") (prog1 ;; Possibly in a component folder, but lets makes sure its not just the LWC folder. (if (string= current-folder "lwc") (prog1 ;; Not in a component, retrieve project. ;; (message "Retrieving Project...") (sfdx--retrieve nil current-folder)) ;; In a component, retrieve component. ;; (message "Retrieving Component...") (sfdx--retrieve t current-folder))) (prog1 ;; Are we in a project? (if (locate-dominating-file buffer-file-name "force-app") (prog1 ;; In project, retrieve project. ;; (message "Retrieving Project...") (sfdx--retrieve nil current-folder)) (prog1 ;; Not in an SFDX project. (message "You are not in a component folder or an SFDX project!")) ) ) ) ) ) (define-transient-command sfdx/transient-action () "SFDX CLI Actions" ["Project Specific" ("P" "Create New Project" sfdx/create-project)] ["Create Component" ("n" "create new" sfdx/create-component)] ["Actions for this Component" ("d" "deploy" sfdx/deploy-component-or-project) ("r" "retrieve" sfdx/retrieve-component)] ["Download Component" ("f" "fetch by component name" sfdx/fetch-component)]) (provide 'sfdx) ;;; sfdx.el ends here