-
Notifications
You must be signed in to change notification settings - Fork 0
feat(lib/salesforce): add mkSfPlugin and sfWithPlugins for oclif plugin support #83
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
Merged
Merged
Changes from all commits
Commits
Show all changes
3 commits
Select commit
Hold shift + click to select a range
90bcbff
feat(lib/salesforce): add mkSfPlugin and sfWithPlugins for oclif plug…
UnstoppableMango 15ccd17
fix(lib/salesforce): address review feedback on plugin helpers
UnstoppableMango d700a63
fix(lib/salesforce): reject lockfileVersion 1 in mkSfPlugin
UnstoppableMango File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,10 @@ | ||
| { pkgs }: | ||
| let | ||
| callPackage = pkgs.lib.callPackageWith (packages // pkgs); | ||
|
|
||
| packages = { | ||
| mkSfPlugin = callPackage ./plugin.nix { }; | ||
| sfWithPlugins = callPackage ./with-plugins.nix { }; | ||
| }; | ||
| in | ||
| packages | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,93 @@ | ||
| { | ||
| lib, | ||
| buildNpmPackage, | ||
| fetchurl, | ||
| jq, | ||
| }: | ||
|
|
||
| # Builds an oclif plugin published to the npm registry into | ||
| # $out/lib/node_modules/<npmName>, ready for `salesforce-cli.withPlugins`. | ||
| { | ||
| pname, | ||
| version, | ||
| # Registry name, e.g. "@salesforce/plugin-code-analyzer". | ||
| npmName, | ||
| # Hash of the registry tarball. | ||
| hash, | ||
| npmDepsHash, | ||
| # Packages a command of this plugin shells out to. `withPlugins` puts them on | ||
| # the PATH of the CLI it builds; a plugin derivation on its own has no bin to | ||
| # wrap, so this is the only place they can be attached. | ||
| runtimeInputs ? [ ], | ||
| passthru ? { }, | ||
| ... | ||
| }@args: | ||
| let | ||
| # Scoped names live under the scope on the registry but drop it from the | ||
| # tarball filename: @salesforce/plugin-x/-/plugin-x-1.0.0.tgz. | ||
| tarballName = lib.last (lib.splitString "/" npmName); | ||
| in | ||
| buildNpmPackage ( | ||
| removeAttrs args [ | ||
| "npmName" | ||
| "hash" | ||
| "runtimeInputs" | ||
| "passthru" | ||
| ] | ||
| // { | ||
| inherit pname version npmDepsHash; | ||
|
|
||
| src = fetchurl { | ||
| url = "https://registry.npmjs.org/${npmName}/-/${tarballName}-${version}.tgz"; | ||
| inherit hash; | ||
| }; | ||
|
|
||
| npmDepsFetcherVersion = 2; | ||
|
|
||
| # Salesforce publishes plugins with an npm-shrinkwrap.json (plugin-trust | ||
| # signs it) whose dev subtree is incomplete, so `npm ci` falls back to the | ||
| # registry for the missing entries and fails in the sandbox. dist/ ships | ||
| # prebuilt, so drop the dev half of the lockfile and of package.json with | ||
| # it. jq is called by store path because buildNpmPackage replays postPatch | ||
| # inside fetchNpmDeps, whose build environment it does not control. | ||
| postPatch = '' | ||
| if [ -f npm-shrinkwrap.json ]; then | ||
| lockfile=npm-shrinkwrap.json | ||
| elif [ -f package-lock.json ]; then | ||
| lockfile=package-lock.json | ||
| else | ||
| echo "mkSfPlugin: ${npmName} ships no npm-shrinkwrap.json or package-lock.json" >&2 | ||
| exit 1 | ||
| fi | ||
|
|
||
| # The prune below walks .packages, which only exists from lockfileVersion | ||
| # 2 on. npm has written 2 or 3 since npm 7, so a version 1 lockfile is | ||
| # rejected rather than given a second pruning path. | ||
| lockfileVersion=$(${lib.getExe jq} '.lockfileVersion // 0' "$lockfile") | ||
| if [ "$lockfileVersion" -lt 2 ]; then | ||
| echo "mkSfPlugin: ${npmName} ships $lockfile at lockfileVersion $lockfileVersion, which has no .packages tree" >&2 | ||
| exit 1 | ||
| fi | ||
|
|
||
| ${lib.getExe jq} 'del(.devDependencies)' package.json >patched.json | ||
| mv patched.json package.json | ||
|
|
||
| ${lib.getExe jq} ' | ||
| (.packages |= with_entries(select(.value.dev != true))) | ||
| | del(.packages[""].devDependencies) | ||
| ' "$lockfile" >patched.json | ||
|
coderabbitai[bot] marked this conversation as resolved.
|
||
| mv patched.json "$lockfile" | ||
| ''; | ||
|
|
||
| # dist/ ships prebuilt, and the install scripts are husky and telemetry. | ||
| npmFlags = [ | ||
| "--ignore-scripts" | ||
| "--legacy-peer-deps" | ||
| ]; | ||
| dontNpmBuild = true; | ||
|
|
||
| passthru = passthru // { | ||
| inherit npmName runtimeInputs; | ||
| }; | ||
| } | ||
| ) | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,113 @@ | ||
| { | ||
| lib, | ||
| jq, | ||
| makeWrapper, | ||
| nodejs, | ||
| runCommand, | ||
| }: | ||
|
|
||
| # Rebuilds the Salesforce CLI with `plugins` linked in as core plugins, so they | ||
| # resolve from the store instead of from `sf plugins install`'s mutable copy | ||
| # under $XDG_DATA_HOME/sf. | ||
| let | ||
| sfWithPlugins = | ||
| { salesforce-cli, plugins }: | ||
| let | ||
| runtimeInputs = lib.concatMap (plugin: plugin.runtimeInputs or [ ]) plugins; | ||
|
|
||
| wrapperArgs = | ||
| lib.mapAttrsToList ( | ||
| name: value: "--set ${name} ${lib.escapeShellArg value}" | ||
| ) salesforce-cli.runtimeEnv | ||
| ++ lib.optional (runtimeInputs != [ ]) "--prefix PATH : ${lib.makeBinPath runtimeInputs}"; | ||
| in | ||
| runCommand "salesforce-cli-${salesforce-cli.version}" | ||
| { | ||
| nativeBuildInputs = [ | ||
| jq | ||
| makeWrapper | ||
| ]; | ||
|
|
||
| inherit plugins; | ||
| pluginNames = builtins.toJSON (map (plugin: plugin.npmName) plugins); | ||
| pluginDeps = builtins.toJSON ( | ||
| lib.listToAttrs (lib.map (plugin: lib.nameValuePair plugin.npmName plugin.version) plugins) | ||
| ); | ||
|
|
||
| inherit (salesforce-cli) pname version meta; | ||
|
|
||
| # Updates target pkgs/salesforce-cli/default.nix, so the base package's | ||
| # updateScript does not carry over to the composition. | ||
| passthru = removeAttrs salesforce-cli.passthru [ "updateScript" ] // { | ||
| inherit plugins; | ||
| # Stack onto what is already linked in rather than starting from the | ||
| # bare CLI again. | ||
| withPlugins = | ||
| extra: | ||
| sfWithPlugins { | ||
| inherit salesforce-cli; | ||
| plugins = plugins ++ extra; | ||
| }; | ||
| }; | ||
| } | ||
| '' | ||
| upstream=${salesforce-cli}/lib/node_modules/@salesforce/cli | ||
| root=$out/lib/node_modules/@salesforce/cli | ||
| mkdir -p "$root/node_modules" | ||
|
|
||
| # The CLI's own ~2M of files are copied rather than symlinked: node resolves | ||
| # a module's dependencies from its realpath, so a symlinked entry point | ||
| # would look for node_modules beside the upstream copy and never see the | ||
| # plugins linked in below. | ||
| for entry in $(ls -A "$upstream"); do | ||
| if [ "$entry" != node_modules ]; then | ||
| cp -r --no-preserve=mode,ownership "$upstream/$entry" "$root/$entry" | ||
| fi | ||
| done | ||
|
|
||
| # Its 270M of dependencies stay symlinks. Scope directories are recreated as | ||
| # real directories so a scoped plugin can be linked in beside them. | ||
| link_into() { | ||
| local from="$1" | ||
| for entry in $(ls -A "$from"); do | ||
| case "$entry" in | ||
| @*) | ||
| mkdir -p "$root/node_modules/$entry" | ||
| for scoped in $(ls -A "$from/$entry"); do | ||
| ln -sfn "$from/$entry/$scoped" "$root/node_modules/$entry/$scoped" | ||
| done | ||
| ;; | ||
| *) | ||
| ln -sfn "$from/$entry" "$root/node_modules/$entry" | ||
| ;; | ||
| esac | ||
| done | ||
| } | ||
|
|
||
| link_into "$upstream/node_modules" | ||
| for plugin in $plugins; do | ||
| link_into "$plugin/lib/node_modules" | ||
| done | ||
|
|
||
| # oclif loads a core plugin only when it appears in both oclif.plugins and | ||
| # dependencies (see loadCorePlugins in @oclif/core), and a plugin that is | ||
| # also declared under oclif.jitPlugins would otherwise still be reported as | ||
| # not installed. | ||
| jq --argjson names "$pluginNames" --argjson deps "$pluginDeps" ' | ||
| .oclif.plugins += $names | ||
| | .dependencies += $deps | ||
| | if .oclif.jitPlugins | ||
| then .oclif.jitPlugins |= with_entries(select(.key as $k | $names | index($k) | not)) | ||
| else . end | ||
| ' "$root/package.json" >package.json | ||
| mv package.json "$root/package.json" | ||
|
|
||
| mkdir -p $out/bin | ||
| for bin in sf sfdx; do | ||
| makeWrapper ${lib.getExe nodejs} "$out/bin/$bin" \ | ||
| --add-flags "--no-deprecation $root/bin/run.js" \ | ||
| ${lib.concatStringsSep " " wrapperArgs} | ||
| done | ||
| ''; | ||
| in | ||
| sfWithPlugins |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.