diff --git a/.github/workflows/deploy-npm.yml b/.github/workflows/deploy-npm.yml index 02fb6d1d4..472a69684 100644 --- a/.github/workflows/deploy-npm.yml +++ b/.github/workflows/deploy-npm.yml @@ -12,9 +12,6 @@ on: github-token: description: "The github token" required: true - npm-token: - description: "The npm deploy token" - required: true jobs: deploy-npm: @@ -28,7 +25,6 @@ jobs: uses: actions/setup-node@v4 with: node-version: ${{ inputs.node-version }} - registry-url: https://registry.npmjs.org cache: npm - name: Install dependencies run: npm clean-install --prefer-offline --frozen-lockfile @@ -36,26 +32,24 @@ jobs: run: npm run lint - name: Run unit tests run: npm run test:unit - - name: Update edge release alias - shell: bash - run: | - if ./scripts/semcompare.sh "${{ github.event.release.tag_name }}" "$(cat ./release-aliases/3-EDGE)"; then - echo "${{ github.event.release.tag_name }}" > ./release-aliases/3-EDGE - fi - - name: Update stable release alias - shell: bash - if: github.event.release.prerelease == false - run: | - if ./scripts/semcompare.sh "${{ github.event.release.tag_name }}" "$(cat ./release-aliases/3-STABLE)"; then - echo "${{ github.event.release.tag_name }}" > ./release-aliases/3-STABLE - fi + #- name: Update edge release alias + # shell: bash + # run: | + # if ./scripts/semcompare.sh "${{ github.event.release.tag_name }}" "$(cat ./release-aliases/3-EDGE)"; then + # echo "${{ github.event.release.tag_name }}" > ./release-aliases/3-EDGE + # fi + #- name: Update stable release alias + # shell: bash + # if: github.event.release.prerelease == false + # run: | + # if ./scripts/semcompare.sh "${{ github.event.release.tag_name }}" "$(cat ./release-aliases/3-STABLE)"; then + # echo "${{ github.event.release.tag_name }}" > ./release-aliases/3-STABLE + # fi - name: Prepare Release uses: lando/prepare-release-action@v3 with: lando-plugin: true - sync-token: ${{ secrets.github-token }} - sync-email: rtfm47@lando.dev - sync-username: rtfm-47 + sync: false - name: Upgrade npm for trusted publishing run: npm install -g "npm@^11.5.1" - name: Publish to npm @@ -64,9 +58,9 @@ jobs: PACKAGE=$(node -p "require('./package.json').name") if [ "${{ github.event.release.prerelease }}" == "false" ]; then - npm publish --access public --dry-run - npm publish --access public - npm dist-tag add "$PACKAGE@$VERSION" edge + npm publish --access public --tag latest --dry-run + npm publish --access public --tag latest + # npm dist-tag add "$PACKAGE@$VERSION" edge # not supported with trusted publishing echo "::notice title=Published $VERSION to $PACKAGE::This is a stable release published to the default 'latest' npm tag" echo "::notice title=Updated latest tag to $VERSION::The stable tag now points to $VERSION" @@ -78,16 +72,14 @@ jobs: echo "::notice title=Published $VERSION to $PACKAGE::This is a prerelease published to the 'edge' npm tag" echo "::notice title=Updated edge tag to $VERSION::The edge tag now points to $VERSION" fi - env: - NODE_AUTH_TOKEN: ${{ secrets.npm-token }} - - name: Update edge release alias on main - if: github.event.release.target_commitish == 'edge' - run: | - git clone https://github.com/lando/core.git core - cd core - git config user.name "rtfm-47" - git config user.email "rtfm47@lando.dev" - echo "${{ github.event.release.tag_name }}" > ./release-aliases/3-EDGE - git add . - git commit -m "Update edge release alias to ${{ github.event.release.tag_name }} triggered by @rtfm-47" - git push https://x-access-token:${{ secrets.github-token }}@github.com/lando/core.git main + #- name: Update edge release alias on main + # if: github.event.release.target_commitish == 'edge' + # run: | + # git clone https://github.com/lando/core.git core + # cd core + # git config user.name "rtfm-47" + # git config user.email "rtfm47@lando.dev" + # echo "${{ github.event.release.tag_name }}" > ./release-aliases/3-EDGE + # git add . + # git commit -m "Update edge release alias to ${{ github.event.release.tag_name }} triggered by @rtfm-47" + # git push https://x-access-token:${{ secrets.github-token }}@github.com/lando/core.git main diff --git a/.github/workflows/dev-release.yml b/.github/workflows/dev-release.yml index 5d0313e00..018dd4f93 100644 --- a/.github/workflows/dev-release.yml +++ b/.github/workflows/dev-release.yml @@ -29,38 +29,38 @@ jobs: os: ${{ matrix.os }} version: dev - sign: - uses: ./.github/workflows/sign-binary.yml - needs: - - package - strategy: - fail-fast: false - matrix: - file: - - lando-linux-arm64-${{ github.sha }} - - lando-macos-arm64-${{ github.sha }} - - lando-win-arm64-${{ github.sha }} + #sign: + # uses: ./.github/workflows/sign-binary.yml + # needs: + # - package + # strategy: + # fail-fast: false + # matrix: + # file: + # - lando-linux-arm64-${{ github.sha }} + # - lando-macos-arm64-${{ github.sha }} + # - lando-win-arm64-${{ github.sha }} - - lando-linux-x64-${{ github.sha }} - - lando-macos-x64-${{ github.sha }} - - lando-win-x64-${{ github.sha }} + # - lando-linux-x64-${{ github.sha }} + # - lando-macos-x64-${{ github.sha }} + # - lando-win-x64-${{ github.sha }} - with: - download-pattern: packaged-lando-* - file: ${{ matrix.file }} - secrets: - apple-notary-user: ${{ secrets.APPLE_NOTARY_USER }} - apple-notary-password: ${{ secrets.APPLE_NOTARY_PASSWORD }} - certificate-data: ${{ contains(matrix.file, 'macos') && secrets.APPLE_CERT_DATA || secrets.KEYLOCKER_CLIENT_CERT }} - certificate-password: ${{ contains(matrix.file, 'macos') && secrets.APPLE_CERT_PASSWORD || secrets.KEYLOCKER_CLIENT_CERT_PASSWORD }} - keylocker-api-key: ${{ secrets.KEYLOCKER_API_KEY }} - keylocker-cert-sha1-hash: ${{ secrets.KEYLOCKER_CERT_SHA1_HASH }} - keylocker-keypair-alias: ${{ secrets.KEYLOCKER_KEYPAIR_ALIAS }} + # with: + # download-pattern: packaged-lando-* + # file: ${{ matrix.file }} + # secrets: + # apple-notary-user: ${{ secrets.APPLE_NOTARY_USER }} + # apple-notary-password: ${{ secrets.APPLE_NOTARY_PASSWORD }} + # certificate-data: ${{ contains(matrix.file, 'macos') && secrets.APPLE_CERT_DATA || secrets.KEYLOCKER_CLIENT_CERT }} + # certificate-password: ${{ contains(matrix.file, 'macos') && secrets.APPLE_CERT_PASSWORD || secrets.KEYLOCKER_CLIENT_CERT_PASSWORD }} + # keylocker-api-key: ${{ secrets.KEYLOCKER_API_KEY }} + # keylocker-cert-sha1-hash: ${{ secrets.KEYLOCKER_CERT_SHA1_HASH }} + # keylocker-keypair-alias: ${{ secrets.KEYLOCKER_KEYPAIR_ALIAS }} build-release-binary-alias: uses: ./.github/workflows/release-rename-binary.yml needs: - - sign + - package strategy: fail-fast: false matrix: @@ -77,11 +77,11 @@ jobs: with: source: lando-${{ matrix.os }}-${{ matrix.arch }}-${{ github.sha }} destination: lando-${{ matrix.os }}-${{ matrix.arch }}-${{ matrix.alias }} - download-pattern: signed-lando-* + download-pattern: packaged-lando-* build-release-binary-branch: uses: ./.github/workflows/release-rename-binary.yml needs: - - sign + - package strategy: fail-fast: false matrix: @@ -96,7 +96,7 @@ jobs: with: source: lando-${{ matrix.os }}-${{ matrix.arch }}-${{ github.sha }} destination: lando-${{ matrix.os }}-${{ matrix.arch }}-${{ github.head_ref || github.ref_name }} - download-pattern: signed-lando-* + download-pattern: packaged-lando-* checksum: uses: ./.github/workflows/generate-checksums.yml @@ -116,16 +116,16 @@ jobs: show: true upload-name: release-checksums-${{ matrix.alias }} - deploy-releases-s3: - uses: ./.github/workflows/deploy-s3.yml - needs: - - checksum - with: - download-pattern: release-* - secrets: - aws-secret-access-key: ${{ secrets.S3_SECRET_ACCESS_KEY }} - aws-access-key-id: ${{ secrets.S3_ACCESS_KEY_ID }} - aws-region: us-east-1 + #deploy-releases-s3: + # uses: ./.github/workflows/deploy-s3.yml + # needs: + # - checksum + # with: + # download-pattern: release-* + # secrets: + # aws-secret-access-key: ${{ secrets.S3_SECRET_ACCESS_KEY }} + # aws-access-key-id: ${{ secrets.S3_ACCESS_KEY_ID }} + # aws-region: us-east-1 deploy-releases-artifacts: uses: ./.github/workflows/deploy-artifacts.yml needs: diff --git a/.github/workflows/pkg-binary.yml b/.github/workflows/pkg-binary.yml index 13a9e0994..9ff4df142 100644 --- a/.github/workflows/pkg-binary.yml +++ b/.github/workflows/pkg-binary.yml @@ -51,8 +51,8 @@ jobs: cache: npm - name: Install dependencies run: npm clean-install --prefer-offline --frozen-lockfile --production - - name: Install plugins - run: scripts/install-plugins.sh --lando bin/lando ${{ inputs.edge == true && '--edge' || '' }} + #- name: Install plugins + # run: scripts/install-plugins.sh --lando bin/lando ${{ inputs.edge == true && '--edge' || '' }} - name: Switch to edge channel if: inputs.edge == true run: | @@ -71,7 +71,7 @@ jobs: node-version: ${{ inputs.node-version }} os: ${{ inputs.os }} options: --options dns-result-order=ipv4first - pkg: "@yao-pkg/pkg@5.16.1" + pkg: "@yao-pkg/pkg@6.4.0" upload-key: "packaged-${{ inputs.filename }}-${{ inputs.os }}-${{ inputs.arch }}-${{ github.sha }}" - name: Ensure version if: (inputs.os == 'linux' && runner.os == 'Linux') || (inputs.os == 'macos' && runner.os == 'macOS') @@ -82,8 +82,8 @@ jobs: - name: Ensure channel if: (inputs.os == 'linux' && runner.os == 'Linux') || (inputs.os == 'macos' && runner.os == 'macOS') run: ./dist/${{ inputs.filename }} config --path channel | grep ${{ inputs.edge == true && 'edge' || 'stable' }} - - name: Ensure plugin install - if: ((inputs.os == 'linux' && runner.os == 'Linux') || (inputs.os == 'macos' && runner.os == 'macOS')) - run: | - ./dist/${{ inputs.filename }} config --path fatcore | grep true - ./dist/${{ inputs.filename }} config | grep -q "/snapshot/core/plugins/wordpress" + #- name: Ensure plugin install + # if: ((inputs.os == 'linux' && runner.os == 'Linux') || (inputs.os == 'macos' && runner.os == 'macOS')) + # run: | + # ./dist/${{ inputs.filename }} config --path fatcore | grep true + # ./dist/${{ inputs.filename }} config | grep -q "/snapshot/core/plugins/wordpress" diff --git a/.github/workflows/pr-core-tests.yml b/.github/workflows/pr-core-tests.yml index 0bd6950d8..7ca9a2770 100644 --- a/.github/workflows/pr-core-tests.yml +++ b/.github/workflows/pr-core-tests.yml @@ -103,7 +103,7 @@ jobs: node-version: ${{ matrix.node-version }} options: --options dns-result-order=ipv4first upload: false - pkg: "@yao-pkg/pkg@5.16.1" + pkg: "@yao-pkg/pkg@6.4.0" - name: Install full deps run: npm clean-install --prefer-offline --frozen-lockfile - name: Setup lando ${{ steps.pkg-action.outputs.file }} diff --git a/.github/workflows/pr-docs-tests.yml b/.github/workflows/pr-docs-tests.yml index 9e0cb17ae..e146987f4 100644 --- a/.github/workflows/pr-docs-tests.yml +++ b/.github/workflows/pr-docs-tests.yml @@ -74,7 +74,7 @@ jobs: node-version: ${{ matrix.node-version }} options: --options dns-result-order=ipv4first upload: false - pkg: "@yao-pkg/pkg@5.16.1" + pkg: "@yao-pkg/pkg@6.4.0" - name: Install full deps run: npm clean-install --prefer-offline --frozen-lockfile - name: Setup lando ${{ steps.pkg-action.outputs.file }} diff --git a/.github/workflows/pr-setup-linux-tests.yml b/.github/workflows/pr-setup-linux-tests.yml index 682ab7d98..6984bcb31 100644 --- a/.github/workflows/pr-setup-linux-tests.yml +++ b/.github/workflows/pr-setup-linux-tests.yml @@ -44,7 +44,7 @@ jobs: node-version: ${{ matrix.node-version }} options: --options dns-result-order=ipv4first upload: false - pkg: "@yao-pkg/pkg@5.16.1" + pkg: "@yao-pkg/pkg@6.4.0" - name: Install full deps run: npm clean-install --prefer-offline --frozen-lockfile - name: Setup lando ${{ steps.pkg-action.outputs.file }} diff --git a/.github/workflows/pr-setup-macos-tests.yml b/.github/workflows/pr-setup-macos-tests.yml index 61ed8b539..a1fa5c8b6 100644 --- a/.github/workflows/pr-setup-macos-tests.yml +++ b/.github/workflows/pr-setup-macos-tests.yml @@ -47,7 +47,7 @@ jobs: node-version: ${{ matrix.node-version }} options: --options dns-result-order=ipv4first upload: false - pkg: "@yao-pkg/pkg@5.16.1" + pkg: "@yao-pkg/pkg@6.4.0" - name: Install full deps run: npm clean-install --prefer-offline --frozen-lockfile - name: Setup lando ${{ steps.pkg-action.outputs.file }} diff --git a/.github/workflows/pr-setup-windows-tests.yml b/.github/workflows/pr-setup-windows-tests.yml index 208fd5b6c..02dc67f34 100644 --- a/.github/workflows/pr-setup-windows-tests.yml +++ b/.github/workflows/pr-setup-windows-tests.yml @@ -43,7 +43,7 @@ jobs: node-version: ${{ matrix.node-version }} options: --options dns-result-order=ipv4first upload: false - pkg: "@yao-pkg/pkg@5.16.1" + pkg: "@yao-pkg/pkg@6.4.0" - name: Install full deps run: npm clean-install --prefer-offline --frozen-lockfile - name: Setup lando ${{ steps.pkg-action.outputs.file }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index df1d18807..06c2fa13d 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -31,38 +31,38 @@ jobs: os: ${{ matrix.os }} version: ${{ github.event.release.tag_name }} - sign: - uses: ./.github/workflows/sign-binary.yml - needs: - - package - strategy: - fail-fast: false - matrix: - file: - - lando-linux-arm64-${{ github.ref_name }} - - lando-macos-arm64-${{ github.ref_name }} - - lando-win-arm64-${{ github.ref_name }} - - - lando-linux-x64-${{ github.ref_name }} - - lando-macos-x64-${{ github.ref_name }} - - lando-win-x64-${{ github.ref_name }} - - with: - download-pattern: packaged-lando-* - file: ${{ matrix.file }} - secrets: - apple-notary-user: ${{ secrets.APPLE_NOTARY_USER }} - apple-notary-password: ${{ secrets.APPLE_NOTARY_PASSWORD }} - certificate-data: ${{ contains(matrix.file, 'macos') && secrets.APPLE_CERT_DATA || secrets.KEYLOCKER_CLIENT_CERT }} - certificate-password: ${{ contains(matrix.file, 'macos') && secrets.APPLE_CERT_PASSWORD || secrets.KEYLOCKER_CLIENT_CERT_PASSWORD }} - keylocker-api-key: ${{ secrets.KEYLOCKER_API_KEY }} - keylocker-cert-sha1-hash: ${{ secrets.KEYLOCKER_CERT_SHA1_HASH }} - keylocker-keypair-alias: ${{ secrets.KEYLOCKER_KEYPAIR_ALIAS }} +# sign: +# uses: ./.github/workflows/sign-binary.yml +# needs: +# - package +# strategy: +# fail-fast: false +# matrix: +# file: +# - lando-linux-arm64-${{ github.ref_name }} +# - lando-macos-arm64-${{ github.ref_name }} +# - lando-win-arm64-${{ github.ref_name }} + +# - lando-linux-x64-${{ github.ref_name }} +# - lando-macos-x64-${{ github.ref_name }} +# - lando-win-x64-${{ github.ref_name }} + +# with: +# download-pattern: packaged-lando-* +# file: ${{ matrix.file }} +# secrets: +# apple-notary-user: ${{ secrets.APPLE_NOTARY_USER }} +# apple-notary-password: ${{ secrets.APPLE_NOTARY_PASSWORD }} +# certificate-data: ${{ contains(matrix.file, 'macos') && secrets.APPLE_CERT_DATA || secrets.KEYLOCKER_CLIENT_CERT }} +# certificate-password: ${{ contains(matrix.file, 'macos') && secrets.APPLE_CERT_PASSWORD || secrets.KEYLOCKER_CLIENT_CERT_PASSWORD }} +# keylocker-api-key: ${{ secrets.KEYLOCKER_API_KEY }} +# keylocker-cert-sha1-hash: ${{ secrets.KEYLOCKER_CERT_SHA1_HASH }} +# keylocker-keypair-alias: ${{ secrets.KEYLOCKER_KEYPAIR_ALIAS }} build-release-binary-alias: uses: ./.github/workflows/release-rename-binary.yml needs: - - sign + - package strategy: fail-fast: false matrix: @@ -78,11 +78,11 @@ jobs: with: source: lando-${{ matrix.os }}-${{ matrix.arch }}-${{ github.ref_name }} destination: lando-${{ matrix.os }}-${{ matrix.arch }}-${{ matrix.alias }} - download-pattern: signed-lando-* + download-pattern: packaged-lando-* build-release-binary-tag: uses: ./.github/workflows/release-rename-binary.yml needs: - - sign + - package strategy: fail-fast: false matrix: @@ -96,7 +96,7 @@ jobs: with: source: lando-${{ matrix.os }}-${{ matrix.arch }}-${{ github.ref_name }} destination: lando-${{ matrix.os }}-${{ matrix.arch }}-${{ github.ref_name }} - download-pattern: signed-lando-* + download-pattern: packaged-lando-* checksum: uses: ./.github/workflows/generate-checksums.yml @@ -127,17 +127,17 @@ jobs: show: true upload-name: release-checksums${{ matrix.alias }} - deploy-releases-s3: - uses: ./.github/workflows/deploy-s3.yml - needs: - - checksum - - checksum-s3-aliases - with: - download-pattern: release-* - secrets: - aws-secret-access-key: ${{ secrets.S3_SECRET_ACCESS_KEY }} - aws-access-key-id: ${{ secrets.S3_ACCESS_KEY_ID }} - aws-region: us-east-1 +# deploy-releases-s3: +# uses: ./.github/workflows/deploy-s3.yml +# needs: +# - checksum +# - checksum-s3-aliases +# with: +# download-pattern: release-* +# secrets: +# aws-secret-access-key: ${{ secrets.S3_SECRET_ACCESS_KEY }} +# aws-access-key-id: ${{ secrets.S3_ACCESS_KEY_ID }} +# aws-region: us-east-1 deploy-releases-artifacts: uses: ./.github/workflows/deploy-artifacts.yml needs: @@ -156,52 +156,51 @@ jobs: - checksum secrets: github-token: ${{ secrets.RTFM47_COAXIUM_INJECTOR }} - npm-token: ${{ secrets.NPM_DEPLOY_TOKEN }} - deploy-legacy-notifications: - runs-on: ubuntu-24.04 - needs: - - checksum - env: - TERM: xterm - steps: - - name: Push release to lando/lando - uses: softprops/action-gh-release@v2 - with: - repository: lando/lando - name: ${{ github.event.release.tag_name }} - draft: ${{ github.event.release.draft }} - prerelease: ${{ github.event.release.prerelease }} - tag_name: ${{ github.event.release.tag_name }} - token: ${{ secrets.RTFM47_COAXIUM_INJECTOR }} - body: | - **Starting with v3.21.0-beta.18, Lando is no longer distributed via package installers in here in this releases page!** - - To install Lando please visit the [official install docs](https://docs.lando.dev/install). - - ## Changelogs - - Lando now runs as a distributed plugin-based ecosystem so you will want to check the releases/changelogs in - the various [plugins](https://docs.lando.dev/plugins.html) for relevant notes. - - [Click Here](https://github.com/lando/core/releases/tag/${{ github.event.release.tag_name }}) to check out the notes for `@lando/core@${{ github.event.release.tag_name }}`. - - ## Notes - - * We will continue to push releases here for backwards compatibility, posterity, etc - * [Extended release notes](https://lando.dev/blog/2024/01/16/v321-extended.html) - - - name: Push release to lando/cli - uses: softprops/action-gh-release@v2 - with: - repository: lando/legacy-cli - name: ${{ github.event.release.tag_name }} - draft: ${{ github.event.release.draft }} - prerelease: ${{ github.event.release.prerelease }} - tag_name: ${{ github.event.release.tag_name }} - token: ${{ secrets.RTFM47_COAXIUM_INJECTOR }} - body: | - **Starting with v3.23.0, Lando CLI binaries are no longer distributed here in these releases!** - - They are now available in the `@lando/core` [releases page](https://github.com/lando/core/releases) including [this ${{ github.event.release.tag_name }} release](https://github.com/lando/core/releases/tag/${{ github.event.release.tag_name }}). - - All that said we don't recommned you use these binaries directly. Instead, to install Lando please visit the [official install docs](https://docs.lando.dev/install). +# deploy-legacy-notifications: +# runs-on: ubuntu-24.04 +# needs: +# - checksum +# env: +# TERM: xterm +# steps: +# - name: Push release to lando/lando +# uses: softprops/action-gh-release@v2 +# with: +# repository: lando/lando +# name: ${{ github.event.release.tag_name }} +# draft: ${{ github.event.release.draft }} +# prerelease: ${{ github.event.release.prerelease }} +# tag_name: ${{ github.event.release.tag_name }} +# token: ${{ secrets.RTFM47_COAXIUM_INJECTOR }} +# body: | +# **Starting with v3.21.0-beta.18, Lando is no longer distributed via package installers in here in this releases page!*#* + +# To install Lando please visit the [official install docs](https://docs.lando.dev/install)#. + +# ## Changelogs + +# Lando now runs as a distributed plugin-based ecosystem so you will want to check the releases/changelogs in +# the various [plugins](https://docs.lando.dev/plugins.html) for relevant notes#. + +# [Click Here](https://github.com/lando/core/releases/tag/${{ github.event.release.tag_name }}) to check out the notes for `@lando/core@${{ github.event.release.tag_name }}`#. + +# ## Notes + +# * We will continue to push releases here for backwards compatibility, posterity, etc +# * [Extended release notes](https://lando.dev/blog/2024/01/16/v321-extended.html#) + +# - name: Push release to lando/cli +# uses: softprops/action-gh-release@v2 +# with: +# repository: lando/legacy-cli +# name: ${{ github.event.release.tag_name }} +# draft: ${{ github.event.release.draft }} +# prerelease: ${{ github.event.release.prerelease }} +# tag_name: ${{ github.event.release.tag_name }} +# token: ${{ secrets.RTFM47_COAXIUM_INJECTOR }} +# body: | +# **Starting with v3.23.0, Lando CLI binaries are no longer distributed here in these releases!*#* + +# They are now available in the `@lando/core` [releases page](https://github.com/lando/core/releases) including [this ${{ github.event.release.tag_name }} release](https://github.com/lando/core/releases/tag/${{ github.event.release.tag_name }})#. + +# All that said we don't recommned you use these binaries directly. Instead, to install Lando please visit the [official install docs](https://docs.lando.dev/install). diff --git a/README.md b/README.md index 7fdd3fd5c..05587cfc0 100644 --- a/README.md +++ b/README.md @@ -1,52 +1,5 @@ -# Lando Core +# Flos Lando Core -These are the core libraries that power Lando. They are implemented in [`@lando/cli`] and things like [Pantheon LocalDev](https://pantheon.io/product/localdev) and [WordPress VIP CLI](https://github.com/Automattic/vip-cli/blob/develop/package.json). - -On a high level they serve as: - -**An abstraction layer** Lando vastly reduces the complexity of spinning up containers by exposing only the most relevant config for a given "service" and setting "sane defaults". Lando also provides "recipes" which are common combinations of services and their tooling that satisfy a given development use case - e.g. Drupal, Python, Laravel, Dotnet, etc. - -**A superset** Lando provides ways for developers to run complex commands, build steps and automation on their services without the hassle of custom Dockerfiles or long "docker exec" commands. Think `lando yarn add express`. Think clear my applications cache after I import a database. Think install this core-extension before my appserver starts and then `composer install` after it does. - -**A utility** Lando handles some of the more arduous configuration required for a good Docker Compose setup - e.g. proxying, nice urls, cross-application networking (think Vue.js frontend talking to a separate Laravel backend), host-container file permission handling, file sharing, per-container SSL certificate handling, ssh-key handling, etc. - -## Basic Usage - -```js -const Lando = require('@lando/core'); -const lando = new Lando(config); - -// bootstrap and go -return lando.bootstrap(bsLevel).then(lando => { - lando.getApp().init().then(() => cli.run(getTasks(config, cli.argv()), config)); -}); -const -``` - -For more info you should check out the [docs](https://docs.lando.dev/core/v3): - -## Issues, Questions and Support - -If you have a question or would like some community support we recommend you [join us on Slack](https://launchpass.com/devwithlando). - -If you'd like to report a bug or submit a feature request then please [use the issue queue](https://github.com/lando/core/issues/new/choose) in this repo. - -## Changelog - -We try to log all changes big and small in both [THE CHANGELOG](https://github.com/lando/core/blob/main/CHANGELOG.md) and the [release notes](https://github.com/lando/core/releases). - -## Contributors - - - - - -Made with [contributors-img](https://contrib.rocks).` - -## Other Selected Resources - -* [LICENSE](/LICENSE) -* [TERMS OF USE](https://docs.lando.dev/terms) -* [PRIVACY POLICY](https://docs.lando.dev/privacy) -* [CODE OF CONDUCT](https://docs.lando.dev/coc) +These are the core libraries that power flos version of Lando with seamless compose integration. +Thanks to the upstream [lando-core](https://github.com/lando/core)! diff --git a/app.js b/app.js index 920f230b3..c8b8baa5f 100644 --- a/app.js +++ b/app.js @@ -64,7 +64,6 @@ module.exports = async (app, lando) => { overrides: { tooling: app._coreToolingOverrides, }, - }, {persist: true}); }; @@ -124,6 +123,9 @@ module.exports = async (app, lando) => { // add proxy info as needed app.events.on('post-init', async () => await require('./hooks/app-add-proxy-info')(app, lando)); + // Add _init tooling for bootstrap reference + app.events.on('pre-bootstrap', async () => await require('./hooks/app-add-init-tooling')(app, lando)); + // Collect info so we can inject LANDO_INFO // @NOTE: this is not currently the full lando info because a lot of it requires the app to be on app.events.on('post-init', 10, async () => await require('./hooks/app-set-lando-info')(app, lando)); @@ -144,9 +146,6 @@ module.exports = async (app, lando) => { // v4 parts of the app are ready app.events.on('ready', 6, async () => await require('./hooks/app-v4-ready')(app, lando)); - // this is a gross hack we need to do to reset the engine because the lando 3 runtime has no idea - app.events.on('ready-engine', 1, async () => await require('./hooks/app-reset-orchestrator')(app, lando)); - // Discover portforward true info app.events.on('ready-engine', async () => await require('./hooks/app-set-portforwards')(app, lando)); @@ -199,7 +198,7 @@ module.exports = async (app, lando) => { app.events.on('post-start', async () => await require('./hooks/app-add-proxy-info')(app, lando)); // Add update tip if needed - app.events.on('post-start', async () => await require('./hooks/app-add-path-info')(app, lando)); + // app.events.on('post-start', async () => await require('./hooks/app-add-path-info')(app, lando)); // If we don't have a builtAgainst already then we must be spinning up for the first time and its safe to set this app.events.on('post-start', async () => await require('./hooks/app-update-built-against-post')(app, lando)); diff --git a/bin/lando b/bin/lando index 693186d77..3d0a30728 100755 --- a/bin/lando +++ b/bin/lando @@ -108,6 +108,10 @@ const cores = [ path.resolve(__dirname, '..'), ]; +if (typeof _.get(config, 'plugins.@lando/core') === 'string') { + cores.unshift(path.resolve(appConfig.root, config.plugins['@lando/core'])); +} + // if appConfig points to a different core lets set that here if (typeof _.get(appConfig, 'plugins.@lando/core') === 'string') { cores.unshift(path.resolve(appConfig.root, appConfig.plugins['@lando/core'])); diff --git a/builders/_init.js b/builders/_init.js index a86cf3b04..5b240f560 100644 --- a/builders/_init.js +++ b/builders/_init.js @@ -13,9 +13,11 @@ module.exports = { version: 'custom', type: 'init', name: 'init', + data: null, + dataHome: null, }, builder: (parent, config) => class LandoInit extends parent { - constructor(userConfRoot, home, app, env = {}, labels = {}, image = 'devwithlando/util:4') { + constructor(userConfRoot, home, app, _app, env = {}, labels = {}, image = 'devwithlando/util:4') { // Basic Init service const initService = { services: { @@ -34,7 +36,7 @@ module.exports = { initService.services.init.environment.LANDO_SERVICE_TYPE = 'init'; initService.services.init.labels['io.lando.service-container'] = 'TRUE'; initService.services.init.labels['io.lando.init-container'] = 'TRUE'; - super('init', _.merge({}, config, {env, home, labels, userConfRoot}), initService); + super('init', _.merge({}, config, {env, home, labels, userConfRoot, _app}), initService); } }, }; diff --git a/builders/_lando-compose.js b/builders/_lando-compose.js new file mode 100644 index 000000000..f46ef8d95 --- /dev/null +++ b/builders/_lando-compose.js @@ -0,0 +1,11 @@ +'use strict'; + +module.exports = { + name: '_lando-compose', + parent: '_lando', + builder: parent => class LandoComposeServiceV3 extends parent { + constructor(id, options = {}) { + super(id, options); + } + }, +}; diff --git a/builders/_lando.js b/builders/_lando.js index 3ef39dc51..637312193 100644 --- a/builders/_lando.js +++ b/builders/_lando.js @@ -53,6 +53,8 @@ module.exports = { supportedIgnore = false, root = '', // webroot = '/app', + _app = null, + appMount = '/app', } = {}, ...sources ) { @@ -98,6 +100,10 @@ module.exports = { const environment = { LANDO_SERVICE_NAME: name, LANDO_SERVICE_TYPE: type, + LANDO_WEBROOT_USER: meUser, + LANDO_WEBROOT_GROUP: meUser, + LANDO_MOUNT: appMount, + LANDO_SERVICE_API: 3, }; // Handle labels @@ -110,10 +116,9 @@ module.exports = { // Handle volumes const volumes = [ - `${userConfRoot}:/lando:cached`, + `${userConfRoot}/keys:/lando/keys:cached`, `${globalScriptsDir}:/helpers`, `${entrypointScript}:/lando-entrypoint.sh`, - `${dataHome}:/var/www`, ]; // add in service helpers if we have them @@ -132,10 +137,15 @@ module.exports = { volumes.push(`${addCertsScript}:/scripts/000-add-cert`); volumes.push(`${path.join(userConfRoot, 'certs', certname)}:/certs/cert.crt`); volumes.push(`${path.join(userConfRoot, 'certs', keyname)}:/certs/cert.key`); + volumes.push(`${path.join(userConfRoot, 'certs', certname)}:/lando/certs/${certname}`); + volumes.push(`${path.join(userConfRoot, 'certs', keyname)}:/lando/certs/${keyname}`); + volumes.push(`${userConfRoot}/certs/LandoCA.crt:/lando/certs/LandoCA.crt`); + volumes.push(`${userConfRoot}/certs/LandoCA.key:/lando/certs/LandoCA.key`); } // Add in some more dirz if it makes sense - if (home) volumes.push(`${home}:/user:cached`); + if (home && _.get(_app, '_config.homeMount', true)) volumes.push(`${home}:/user:cached`); + else if (home && _.get(_app, 'config.keys', true)) volumes.push(`${path.join(home, '.ssh')}:/user/.ssh:cached`); // Handle cert refresh // @TODO: this might only be relevant to the proxy, if so let's move it there @@ -178,9 +188,16 @@ module.exports = { // Add named volumes and other thingz into our primary service const namedVols = {}; - _.set(namedVols, data, {}); - _.set(namedVols, dataHome, {}); - + if (null !== data) { + _.set(namedVols, data, {}); + } + if (null !== dataHome) { + _.set(namedVols, dataHome, {}); + volumes.push(`${dataHome}:/var/www`); + } + if (null === entrypoint) { + entrypoint = undefined; + } sources.push({ services: _.set({}, name, { entrypoint, @@ -210,6 +227,7 @@ module.exports = { info.meUser = meUser; info.hasCerts = ssl; info.api = 3; + info.appMount = appMount; // Add the healthcheck if it exists if (healthcheck) info.healthcheck = healthcheck; diff --git a/builders/_proxy.js b/builders/_proxy.js index 1f31be61c..a42dd0808 100644 --- a/builders/_proxy.js +++ b/builders/_proxy.js @@ -6,7 +6,14 @@ const _ = require('lodash'); /* * Helper to get core proxy service */ -const getProxy = ({proxyCommand, proxyPassThru, proxyDomain, userConfRoot, version = 'unknown'} = {}) => { +const getProxy = ({ + proxyCommand, + proxyPassThru, + proxyDomain, + userConfRoot, + proxyConfigDir, + version = 'unknown', +} = {}) => { return { services: { proxy: { @@ -23,7 +30,7 @@ const getProxy = ({proxyCommand, proxyPassThru, proxyDomain, userConfRoot, versi volumes: [ '/var/run/docker.sock:/var/run/docker.sock', `${userConfRoot}/scripts/proxy-certs.sh:/scripts/100-proxy-certs`, - 'proxy_config:/proxy_config', + `${proxyConfigDir}:/proxy_config`, ], }, }, @@ -32,9 +39,6 @@ const getProxy = ({proxyCommand, proxyPassThru, proxyDomain, userConfRoot, versi driver: 'bridge', }, }, - volumes: { - proxy_config: {}, - }, }; }; diff --git a/config.yml b/config.yml index 9d25a6bac..3f8e55692 100644 --- a/config.yml +++ b/config.yml @@ -12,22 +12,22 @@ stats: dockerSupportedVersions: compose: satisfies: "1.x.x || 2.x.x" - recommendUpdate: "<=2.24.6" - tested: "<=2.32.99" + recommendUpdate: "<=2.40.2" + tested: "<=2.40.99" link: linux: https://docs.docker.com/compose/install/#install-compose-on-linux-systems darwin: https://docs.docker.com/desktop/install/mac-install/ win32: https://docs.docker.com/desktop/install/windows-install/ desktop: satisfies: ">=4.0.0 <5" - tested: "<=4.37.99" - recommendUpdate: "<=4.36" + tested: "<=4.54.99" + recommendUpdate: "<=4.53" link: darwin: https://docs.docker.com/desktop/install/mac-install/ win32: https://docs.docker.com/desktop/install/windows-install/ wsl: https://docs.docker.com/desktop/install/windows-install/ engine: - satisfies: ">=18 <28" - tested: "<=27.5.99" + satisfies: ">=18 <30" + tested: "<=29.1.99" link: linux: https://docs.docker.com/engine/install/debian/#install-using-the-convenience-script diff --git a/examples/cache/README.md b/examples/cache/README.md index 9e2ee5e37..4ed4dabd8 100644 --- a/examples/cache/README.md +++ b/examples/cache/README.md @@ -30,10 +30,15 @@ cat ~/.lando/cache/lando-cache.compose.cache || echo $? | grep 1 lando --clear lando || true cat ~/.lando/cache/_.tasks.cache -cat ~/.lando/cache/lando-cache.compose.cache +cat ~/.lando/cache/lando-cache.compose.cache || true # Should regenerate the caches on any --help before the help is displayed lando --clear +# NOTE(flo): Web is not here as the task bootstrapping runs before the app init, which gets compose services :/ +lando exec --help | grep service | grep choices | grep web2 | grep web3 | grep web4 +cat ~/.lando/cache/_.tasks.cache +cat ~/.lando/cache/lando-cache.compose.cache || true +lando exec web -- echo 'Hello World' | grep 'Hello World' lando exec --help | grep service | grep choices | grep web | grep web2 | grep web3 | grep web4 cat ~/.lando/cache/_.tasks.cache cat ~/.lando/cache/lando-cache.compose.cache diff --git a/examples/events/README.md b/examples/events/README.md index 40a2ae4cf..5ad6d8784 100644 --- a/examples/events/README.md +++ b/examples/events/README.md @@ -42,7 +42,7 @@ lando exec web2 -- "cat /app/test/web2-post-stuff.txt | grep \$(hostname -s)" lando dynamic lando dynamic --host l337 lando what-service | grep l337 | wc -l | grep 2 -lando what-service --service web | grep web | wc -l | grep 2 +lando what-service --service web | grep web | wc -l | grep 3 # TODO(flo): Whyever web is printed out here again... lando what-service --service web2 | grep web | wc -l | grep 2 # Should use the app default service as the default in multi-service tooling diff --git a/hooks/app-add-2-landonet.js b/hooks/app-add-2-landonet.js index af8edd685..f61693b9a 100644 --- a/hooks/app-add-2-landonet.js +++ b/hooks/app-add-2-landonet.js @@ -15,7 +15,8 @@ module.exports = async (app, lando) => { return landonet.disconnect({Container: container.id, Force: true}) // Only throw non not connected errors .catch(error => { - if (!_.includes(error.message, 'is not connected to network')) throw error; + if (!_.includes(error.message, 'is not connected to network') && + !_.includes(error.message, 'is not connected to the network')) throw error; }) // Connect .then(() => { diff --git a/hooks/app-add-init-tooling.js b/hooks/app-add-init-tooling.js new file mode 100644 index 000000000..f2801b54e --- /dev/null +++ b/hooks/app-add-init-tooling.js @@ -0,0 +1,21 @@ +'use strict'; + +const _ = require('lodash'); + +module.exports = async (app, lando) => { + if (!_.isEmpty(_.get(app, 'config.tooling', {}))) { + app.log.verbose('additional tooling detected'); + + // Add the _init tasks for the bootstrap event! + // TODO(flo): They are duplicated through "app-add-tooling" but I do not care for now! + _.forEach(require('../utils/get-tooling-tasks')(app.config.tooling, app), task => { + if (task.service !== '_init') { + return; + } + + app.log.debug('adding app cli task %s', task.name); + const injectable = _.has(app, 'engine') ? app : lando; + app.tasks.push(require('../utils/build-tooling-task')(task, injectable)); + }); + } +}; diff --git a/hooks/app-add-proxy-2-landonet.js b/hooks/app-add-proxy-2-landonet.js index 831b251d9..690f2496b 100644 --- a/hooks/app-add-proxy-2-landonet.js +++ b/hooks/app-add-proxy-2-landonet.js @@ -34,7 +34,8 @@ module.exports = async (app, lando) => { return bridgeNet.disconnect({Container: proxyContainer, Force: true}) // Only throw non not connected errors .catch(error => { - if (!_.includes(error.message, 'is not connected to network')) throw error; + if (!_.includes(error.message, 'is not connected to network') && + !_.includes(error.message, 'is not connected to the network')) throw error; }) // Connect .then(() => { diff --git a/hooks/app-add-v3-services.js b/hooks/app-add-v3-services.js index fe3659406..b68e21fc5 100644 --- a/hooks/app-add-v3-services.js +++ b/hooks/app-add-v3-services.js @@ -6,6 +6,13 @@ module.exports = async (app, lando) => { // add parsed services to app object so we can use them downstream app.cachedInfo = _.get(lando.cache.get(app.composeCache), 'info', []); app.parsedServices = require('../utils/parse-v3-services')(_.get(app, 'config.services', {}), app); + app.parsedServices = app.parsedServices.concat( + require('../utils/parse-compose-services')( + _.get(app, 'config.services', {}), + _.keys(_.get(app, 'composeData[0].data[0].services', {})), + app, + ), + ); app.parsedV3Services = _(app.parsedServices).filter(service => service.api === 3).value(); app.servicesList = app.parsedV3Services.map(service => service.name); diff --git a/hooks/app-reset-orchestrator.js b/hooks/app-reset-orchestrator.js deleted file mode 100644 index 0591c1361..000000000 --- a/hooks/app-reset-orchestrator.js +++ /dev/null @@ -1,22 +0,0 @@ -'use strict'; - -module.exports = async (app, lando) => { - // if we dont have an orchestrator bin yet then discover it - if (!lando.config.orchestratorBin) lando.config.orchestratorBin = require('../utils/get-compose-x')(lando.config); - - // because the entire lando 3 runtime was made in a bygone era when we never dreamed of doing stuff like this - // we need this workaround - if (lando._bootstrapLevel >= 3 && !app.engine.composeInstalled) { - app.engine = require('../utils/setup-engine')( - lando.config, - lando.cache, - lando.events, - app.log, - app.shell, - lando.config.instance, - ); - } - - // log our sitch - app.log.debug('using docker-compose %s', lando.config.orchestratorBin); -}; diff --git a/hooks/app-run-events.js b/hooks/app-run-events.js index 921086139..bcd71679d 100644 --- a/hooks/app-run-events.js +++ b/hooks/app-run-events.js @@ -1,9 +1,12 @@ 'use strict'; const _ = require('lodash'); +const remove = require('../utils/remove'); +const path = require('path'); +const formatters = require('../lib/formatters'); module.exports = async (app, lando, cmds, data, event) => { - const eventCommands = require('./../utils/parse-events-config')(cmds, app, data); + const eventCommands = require('./../utils/parse-events-config')(cmds, app, data, lando); // add perm sweeping to all v3 services if (!_.isEmpty(eventCommands)) { const permsweepers = _(eventCommands) @@ -27,7 +30,28 @@ module.exports = async (app, lando, cmds, data, event) => { }); } const injectable = _.has(app, 'engine') ? app : lando; - return injectable.engine.run(eventCommands).catch(err => { + + const splitEventCommands = []; + while (!_.isEmpty(eventCommands)) { + splitEventCommands.push( + _.takeWhile(eventCommands, + (eventCommand, index) => index === 0 || (!!eventCommand.toolingTask === !!eventCommands[index - 1].toolingTask), + ), + ); + eventCommands.splice(0, _.last(splitEventCommands).length); + } + + return lando.Promise.mapSeries(splitEventCommands, eventCommands => { + return lando.Promise.mapSeries(eventCommands, eventCommand => { + if (undefined !== eventCommand.toolingTask) { + const inquiry = formatters.getInteractive(eventCommand.toolingTask.options, eventCommand.answers); + return formatters.handleInteractive(inquiry, eventCommand.answers, eventCommand.toolingTask.command, lando) + .then(answers => eventCommand.toolingTask.run(_.merge(eventCommand.answers, answers))); + } else { + return injectable.engine.run(eventCommands); + } + }); + }).catch(err => { const command = _.tail(event.split('-')).join('-'); if (app.addMessage) { const message = _.trim(_.get(err, 'message')) || 'UNKNOWN ERROR'; @@ -44,5 +68,16 @@ module.exports = async (app, lando, cmds, data, event) => { } else { lando.exitCode = 12; } + }).finally(() => { + const initToolingRunners = _.filter(_.flatten(splitEventCommands), eventCommand => true === eventCommand.isInitEventCommand); + if (_.isEmpty(initToolingRunners)) { + return; + } + const run = _.first(initToolingRunners); + + run.opts = {purge: true, mode: 'attach'}; + return injectable.engine.stop(run) + .then(() => injectable.engine.destroy(run)) + .then(() => remove(path.dirname(run.compose[0]))); }); }; diff --git a/hooks/app-start-proxy.js b/hooks/app-start-proxy.js index 3db065cfb..01043f43a 100644 --- a/hooks/app-start-proxy.js +++ b/hooks/app-start-proxy.js @@ -190,7 +190,7 @@ const parseRoutes = (service, urls = [], sslReady, labels = {}) => { rule.middlewares.push({name: 'lando', key: 'headers.customrequestheaders.X-Lando', value: 'on'}); // Add in any path stripping middleware we need it - if (rule.pathname.length > 1) { + if (rule.pathname.length > 1 && _.get(rule, 'stripPrefix', true)) { rule.middlewares.push({name: 'stripprefix', key: 'stripprefix.prefixes', value: rule.pathname}); } @@ -323,19 +323,17 @@ module.exports = async (app, lando) => { service.labels['traefik.enable'] = true; service.labels['traefik.docker.network'] = lando.config.proxyNet; service.environment.LANDO_PROXY_PASSTHRU = _.toString(lando.config.proxyPassThru); - const proxyVolume = `${lando.config.proxyName}_proxy_config`; return { services: _.set({}, service.name, { networks: {'lando_proxyedge': {}}, labels: service.labels, environment: service.environment, volumes: [ - `${proxyVolume}:/proxy_config`, + `${lando.config.proxyConfigDir}:/proxy_config`, `${lando.config.userConfRoot}/scripts/proxy-certs.sh:/scripts/100-proxy-certs`, ], }), networks: {'lando_proxyedge': {name: lando.config.proxyNet, external: true}}, - volumes: _.set({}, proxyVolume, {external: true}), }; }) diff --git a/hooks/lando-copy-v3-scripts.js b/hooks/lando-copy-v3-scripts.js index e378e352c..4c0293766 100644 --- a/hooks/lando-copy-v3-scripts.js +++ b/hooks/lando-copy-v3-scripts.js @@ -7,8 +7,7 @@ module.exports = async lando => { return lando.Promise.map(lando.config.plugins, plugin => { if (fs.existsSync(plugin.scripts)) { const confDir = path.join(lando.config.userConfRoot, 'scripts'); - const dest = require('../utils/move-config')(plugin.scripts, confDir); - require('../utils/make-executable')(fs.readdirSync(dest), dest); + require('../utils/move-config')(plugin.scripts, confDir); lando.log.debug('automoved scripts from %s to %s and set to mode 755', plugin.scripts, confDir); } }); diff --git a/hooks/lando-get-compat.js b/hooks/lando-get-compat.js index 7acadcf39..2108513cd 100644 --- a/hooks/lando-get-compat.js +++ b/hooks/lando-get-compat.js @@ -5,11 +5,20 @@ const _ = require('lodash'); module.exports = async lando => { // only run if engine bootstrap or above and if engine/orchestrator have been installed if (lando._bootstrapLevel >= 3) { - if (lando.engine.composeInstalled && lando.engine.dockerInstalled ) { + if (lando.engine.composeInstalled && lando.engine.dockerInstalled) { + if (!await lando.cache.isOlderThanMinutes('versions')) { + return; + } + lando.engine.getCompatibility().then(results => { lando.log.verbose('checking docker version compatibility...'); lando.log.debug('compatibility results', _.keyBy(results, 'name')); - lando.cache.set('versions', _.assign(lando.versions, _.keyBy(results, 'name')), {persist: true}); + const lastLandoVersions = _.cloneDeep(lando.versions); + _.assign(lando.versions, _.keyBy(results, 'name')); + if (_.isEqual(lando.versions, lastLandoVersions)) { + return; + } + lando.cache.set('versions', lando.versions, {persist: true}); lando.versions = lando.cache.get('versions'); }); } diff --git a/hooks/lando-run-setup.js b/hooks/lando-run-setup.js index 5392fc401..e799f9b4c 100644 --- a/hooks/lando-run-setup.js +++ b/hooks/lando-run-setup.js @@ -32,7 +32,7 @@ module.exports = async lando => { // reload plugins await lando.reloadPlugins(); // reload needed config - const {orchestratorBin, orchestratorVersion, dockerBin, engineConfig} = require('../utils/build-config')(); + const {orchestratorBin, orchestratorVersion, dockerBin, engineConfig} = require('../utils/build-config')(lando.config); // reset needed config lando.config = {...lando.config, orchestratorBin, orchestratorVersion, dockerBin, engineConfig}; // we need to explicitly reset this for some reason diff --git a/hooks/lando-setup-landonet.js b/hooks/lando-setup-landonet.js index b882e0fc4..751526918 100644 --- a/hooks/lando-setup-landonet.js +++ b/hooks/lando-setup-landonet.js @@ -39,7 +39,7 @@ module.exports = async (lando, options) => { if (lando.engine.dockerInstalled === false) return false; // we also want to do an additional check on docker-destkop - if (lando.config.os.landoPlatform !== 'linux' && !fs.existsSync(getDockerDesktopBin())) return false; + if (!['linux', 'wsl'].includes(lando.config.os.landoPlatform) && !fs.existsSync(getDockerDesktopBin())) return false; // otherwise attempt to sus things out try { diff --git a/hooks/lando-setup-orchestrator.js b/hooks/lando-setup-orchestrator.js index d01dc7735..60b975dc4 100644 --- a/hooks/lando-setup-orchestrator.js +++ b/hooks/lando-setup-orchestrator.js @@ -7,7 +7,7 @@ const path = require('path'); /* * Helper to get docker compose v2 download url */ -const getComposeDownloadUrl = (version = '2.31.0') => { +const getComposeDownloadUrl = (version = '2.40.3') => { const mv = version.split('.')[0] > 1 ? '2' : '1'; const arch = process.arch === 'arm64' ? 'aarch64' : 'x86_64'; const toggle = `${process.platform}-${mv}`; diff --git a/hooks/plugin-auth-from-npmrc.js b/hooks/plugin-auth-from-npmrc.js new file mode 100644 index 000000000..30b380287 --- /dev/null +++ b/hooks/plugin-auth-from-npmrc.js @@ -0,0 +1,25 @@ +'use strict'; + +const write = require('../utils/write-file'); +const fs = require('fs'); +const path = require('path'); +const os = require('os'); +const {parse} = require('ini'); + +module.exports = async lando => { + if (!lando.config.loadNpmrcForPluginAuth) { + return; + } + + const npmrcPath = path.resolve(os.homedir(), '.npmrc'); + if (!fs.existsSync(npmrcPath)) { + return; + } + lando.log.debug('Reading home .npmrc for plugin-auth.json...'); + const content = fs.readFileSync(npmrcPath, { + encoding: 'utf-8', + }); + const data = parse(content); + write(lando.config.pluginConfigFile, data); + lando.plugins.updates = data; +}; diff --git a/index.js b/index.js index 090bed015..4873339cb 100644 --- a/index.js +++ b/index.js @@ -24,7 +24,7 @@ const defaults = { '/entrypoint.sh', '--log.level=DEBUG', '--api.insecure=true', - '--api.dashboard=false', + '--api.dashboard=true', '--providers.docker=true', '--entrypoints.https.address=:443', '--entrypoints.http.address=:80', @@ -81,7 +81,7 @@ module.exports = async lando => { lando.events.once('pre-install-plugins', async options => await require('./hooks/lando-setup-common-plugins')(lando, options)); // move v3 scripts directories as needed - lando.events.on('pre-setup', 0, async () => await require('./hooks/lando-copy-v3-scripts')(lando)); + lando.events.on('post-install-plugins', 0, async () => await require('./hooks/lando-copy-v3-scripts')(lando)); // ensure we setup docker if needed lando.events.once('pre-setup', async options => await require(`./hooks/lando-setup-build-engine-${platform}`)(lando, options)); @@ -100,12 +100,6 @@ module.exports = async lando => { // ensure we setup landonet lando.events.once('pre-setup', async options => await require('./hooks/lando-setup-landonet')(lando, options)); - // also move scripts for init considerations - lando.events.on('pre-init', 0, async () => await require('./hooks/lando-copy-v3-scripts')(lando)); - - // move v3 scripts directories as needed - lando.events.on('pre-init', 0, async () => await require('./hooks/lando-copy-v3-scripts')(lando)); - // set proxy config lando.events.on('post-bootstrap-config', async () => await require('./hooks/lando-set-proxy-config')(lando)); @@ -131,15 +125,14 @@ module.exports = async lando => { // autostart docker if we need to lando.events.once('engine-autostart', async () => await require('./hooks/lando-autostart-engine')(lando)); - // move v3 scripts directories as needed - lando.events.on('pre-engine-start', 0, async () => await require('./hooks/lando-copy-v3-scripts')(lando)); - // clean networks lando.events.on('pre-engine-start', 1, async () => await require('./hooks/lando-clean-networks')(lando)); // regen task cache lando.events.on('before-end', 9999, async () => await require('./hooks/lando-generate-tasks-cache')(lando)); + lando.events.on('post-bootstrap-config', async () => await require('./hooks/plugin-auth-from-npmrc')(lando)); + // return some default things return _.merge({}, defaults, uc(), {config: { appEnv: { diff --git a/lib/app.js b/lib/app.js index f1dc77a76..136ad290f 100644 --- a/lib/app.js +++ b/lib/app.js @@ -6,11 +6,12 @@ const hasher = require('object-hash'); const path = require('path'); const Promise = require('./promise'); const utils = require('./utils'); +const fs = require('node:fs'); /* * Helper to init and then report */ -const initAndReport = (app, method = 'start') => { +const initAndReport = (app, method) => { return app.init().then(() => { app.metrics.report(method, utils.metricsParse(app)); return Promise.resolve(true); @@ -56,7 +57,11 @@ module.exports = class App { * @alias app.name */ this.name = require('../utils/slugify')(name); - this.project = require('../utils/docker-composify')(this.name); + if (lando.config.shouldDockerComposifyProjectName ?? true) { + this.project = require('../utils/docker-composify')(this.name); + } else { + this.project = name; + } this._serviceApi = 3; this._config = lando.config; this._defaultService = 'appserver'; @@ -256,6 +261,8 @@ module.exports = class App { .then(() => this.log.info('destroyed app.')); } + static isBootstrapCommand = undefined; + /** * Initializes the app * @@ -272,14 +279,38 @@ module.exports = class App { init({noEngine = false} = {}) { // We should only need to initialize once, if we have just go right to app ready if (this.initialized) return this.events.emit('ready', this); - // Get compose data if we have any, otherwise set to [] - const composeFiles = require('../utils/load-compose-files')(_.get(this, 'config.compose', []), this.root); - this.composeData = [new this.ComposeService('compose', {}, ...composeFiles)]; - // Validate and set env files - this.envFiles = require('../utils/normalize-files')(_.get(this, 'config.env_file', []), this.root); - // Log some things - this.log.verbose('initiatilizing app at %s...', this.root); + if (App.isBootstrapCommand) { + console.log(require('yargonaut').chalk().cyan('Looks like this is the first time to start the app. Lets bootstrap it...')); + } + + const composeEnvFiles = require('../utils/normalize-files')(_.get(this, 'config.compose_env_file', []), this.root); + return loadPlugins(this, this._lando) + /** + * Event that only gets triggered if the app never started before (or was destroyed) + * + * @since 3.23.25 + * @alias app.events:pre-bootstrap + * @event pre-bootstrap + * @property {App} app The app instance. + */ + .then(() => App.isBootstrapCommand ? this.events.emit('pre-bootstrap', this) : undefined) + // Get compose data if we have any, otherwise set to [] + .then(() => noEngine === true ? [] : require('../utils/load-compose-files')( + _.get(this, 'config.compose', []), + this.root, + this._dir, + (composeFiles, outputFilePath) => + this.engine.getComposeConfig({compose: composeFiles, project: this.project, outputFilePath, opts: {envFiles: composeEnvFiles}}), + )) + .then(composeFileData => { + this.composeData = [new this.ComposeService('compose', {}, ...composeFileData)]; + // Validate and set env files + this.envFiles = require('../utils/normalize-files')(_.get(this, 'config.env_file', []), this.root); + // Log some things + this.log.verbose('initiatilizing app at %s...', this.root); + this.log.silly('app has config', this.config); + }) /** * Event that allows altering of the app object right before it is * initialized. @@ -292,8 +323,7 @@ module.exports = class App { * @event pre_init * @property {App} app The app instance. */ - return loadPlugins(this, this._lando).then(() => this.events.emit('pre-init', this)) - + .then(() => this.events.emit('pre-init', this)) // Actually assemble this thing so its ready for that engine .then(() => { // Get all the services @@ -360,7 +390,7 @@ module.exports = class App { } // Log - this.initialized = true; + this.initialized = !!noEngine; this.log.verbose('app is ready!'); }) /** @@ -374,7 +404,8 @@ module.exports = class App { .then(() => this.events.emit('ready', this)) // @NOTE: dont ask, just continuing to work around v3-wasnt-intended-to-do-this problems - .then(() => noEngine === true ? undefined : this.events.emit('ready-engine', this)); + .then(() => noEngine === true ? undefined : this.events.emit('ready-engine', this)) + .then(() => noEngine === true ? require('../hooks/app-purge-compose-cache')(this, this._lando) : undefined); } /** @@ -489,13 +520,19 @@ module.exports = class App { * @alias app.start * @fires pre_start * @fires post_start + * @fires post_bootstrap * @return {Promise} A Promise. * */ start() { // Log this.log.info('starting app...'); - return initAndReport(this) + + if (undefined === App.isBootstrapCommand) { + App.isBootstrapCommand = !fs.existsSync(this._dir); + } + + return initAndReport(this, 'start') /** * Event that runs before an app starts up. @@ -523,6 +560,17 @@ module.exports = class App { * @event post_start */ .then(() => this.events.emit('post-start')) + + /** + * Event that only gets triggered if the app never started before (or was destroyed) + * + * @since 3.23.25 + * @alias app.events:post-bootstrap + * @event post-bootstrap + * @property {App} app The app instance. + */ + .then(() => App.isBootstrapCommand ? this.events.emit('post-bootstrap', this) : undefined) + .then(() => this.log.info('started app.')); } diff --git a/lib/cache.js b/lib/cache.js index a03dead72..81711dbd2 100644 --- a/lib/cache.js +++ b/lib/cache.js @@ -118,6 +118,18 @@ class Cache extends NodeCache { this.log.debug('No file cache with key %s', key); } } + + async isOlderThanMinutes(key, minutes = 5) { + try { + const stats = await fs.stat(path.join(this.cacheDir, key)); + const modifiedTime = stats.mtime; + const minutesAgo = Date.now() - minutes * 60 * 1000; + + return modifiedTime.getTime() < minutesAgo; + } catch (err) { + return false; + } + } } /* diff --git a/lib/cli.js b/lib/cli.js index 91af82162..85111d5af 100644 --- a/lib/cli.js +++ b/lib/cli.js @@ -320,6 +320,8 @@ module.exports = class Cli { .option('help', globalOptions.help) .option('verbose', globalOptions.verbose) .version(false) + .env('lando_cli_') + .locale('en') .middleware([(argv => { argv._app = config; argv._yargs = yargs; diff --git a/lib/compose.js b/lib/compose.js index 34cc67c18..dccca225e 100644 --- a/lib/compose.js +++ b/lib/compose.js @@ -20,6 +20,21 @@ const composeFlags = { rm: '--rm', timestamps: '--timestamps', volumes: '-v', + outputFilePath: '-o', + ignoreBuildable: '--ignore-buildable', +}; + +const composeFlagOptionMapping = { + build: ['noCache', 'pull', 'q'], + down: ['removeOrphans', 'volumes'], + exec: ['background', 'detach', 'noTTY'], + kill: ['removeOrphans'], + logs: ['follow', 'timestamps'], + ps: ['q'], + pull: ['q', 'ignoreBuildable'], + rm: ['force', 'volumes'], + up: ['background', 'detach', 'noRecreate', 'noDeps', 'pull', 'q', 'recreate', 'removeOrphans', 'timestamps'], + config: ['outputFilePath', 'q'], }; // Default options nad things @@ -30,21 +45,30 @@ const defaultOptions = { kill: {}, logs: {follow: false, timestamps: false}, ps: {q: true}, - pull: {}, + pull: {ignoreBuildable: true}, rm: {force: true, volumes: true}, up: {background: true, noRecreate: true, recreate: false, removeOrphans: true}, + config: {}, }; /* * Helper to merge options with default */ -const mergeOpts = (run, opts = {}) => _.merge({}, defaultOptions[run], opts); +const mergeOpts = (run, opts = {}) => _.merge( + {}, + defaultOptions[run], + _.pickBy( + opts, + (value, index) => _.includes(composeFlagOptionMapping[run], index), + ), +); /* * Parse docker-compose options */ -const parseOptions = (opts = {}) => { - const flags = _.map(composeFlags, (value, key) => _.get(opts, key, false) ? value : ''); +const parseOptions = (run, opts = {}) => { + const composeOpts = mergeOpts(run, _.merge({}, opts, require('yargs').argv)); + const flags = _.map(composeFlags, (value, key) => _.get(composeOpts, key, false) ? value : ''); const environment = _.flatMap(opts.environment, (value, key) => ['--env', `${key}=${value}`]); const user = (_.has(opts, 'user')) ? ['--user', opts.user] : []; const workdir = (_.has(opts, 'workdir')) ? ['--workdir', opts.workdir] : []; @@ -55,21 +79,22 @@ const parseOptions = (opts = {}) => { /* * Helper to standardize construction of docker commands */ -const buildCmd = (run, name, compose, {services, cmd}, opts = {}) => { +const buildCmd = (run, name, compose, {services, cmd, envFiles}, opts = {}) => { if (!name) throw new Error('Need to give this composition a project name!'); // @TODO: we need to strip out opts.user on start/stop because we often get it as part of run const project = ['--project-name', name]; const files = _.flatten(_.map(compose, unit => ['--file', unit])); - const options = parseOptions(opts); + const envFile = _.flatten(_.map(envFiles, unit => ['--env-file', unit])); + const options = parseOptions(run, opts); const argz = _.flatten(_.compact([services, cmd])); - return _.flatten([project, files, run, options, argz]); + return _.flatten([project, files, envFile, run, options, argz]); }; /* * Helper to build build object needed by lando.shell.sh */ const buildShell = (run, name, compose, opts = {}) => ({ - cmd: buildCmd(run, name, compose, {services: opts.services, cmd: opts.cmd}, mergeOpts(run, opts)), + cmd: buildCmd(run, name, compose, {services: opts.services, cmd: opts.cmd, envFiles: opts.envFiles ?? []}, opts), opts: {mode: 'spawn', cstdio: opts.cstdio, silent: opts.silent}, }); @@ -155,3 +180,8 @@ exports.start = (compose, project, opts = {}) => buildShell('up', project, compo * Run docker compose stop */ exports.stop = (compose, project, opts = {}) => buildShell('stop', project, compose, opts); + +/* + * Run docker compose config + */ +exports.config = (compose, project, opts = {}) => buildShell('config', project, compose, opts); diff --git a/lib/daemon.js b/lib/daemon.js index 938beff03..fda2ff13b 100644 --- a/lib/daemon.js +++ b/lib/daemon.js @@ -41,7 +41,7 @@ const buildDockerCmd = (cmd, scriptsDir) => { */ const getMacProp = prop => shell.sh(['defaults', 'read', `${MACOS_BASE}/Contents/Info.plist`, prop]) .then(data => _.trim(data)) - .catch(() => null); + .catch(() => 'skip'); /* * Creates a new Daemon instance. @@ -54,7 +54,7 @@ module.exports = class LandoDaemon { log = new Log(), context = 'node', compose = require('../utils/get-compose-x')(), - orchestratorVersion = '2.31.0', + orchestratorVersion = '2.40.3', userConfRoot = path.join(os.homedir(), '.lando'), ) { this.cache = cache; @@ -223,7 +223,7 @@ module.exports = class LandoDaemon { // Return true if we get a zero response and cache the result try { - await require('../utils/run-command')(docker, ['ps'], {debug: this.debug}); + await require('../utils/run-command')(docker, ['ps', '-q'], {debug: this.debug}); this.debug('engine is up.'); cache.set('engineup', true, {ttl: 5}); this.isRunning = true; diff --git a/lib/docker.js b/lib/docker.js index c38325471..ca784562c 100644 --- a/lib/docker.js +++ b/lib/docker.js @@ -92,7 +92,7 @@ module.exports = class Landerode extends Dockerode { // Filter by app name if an app name was given. .then(containers => { if (options.project) return _.filter(containers, c => c.app === options.project); - else if (options.app) return _.filter(containers, c => c.app === require('../utils/docker-composify')(options.app)); // eslint-disable-line max-len + else if (options.app) return _.filter(containers, c => c.app === options.app); return containers; }) // Then finally filter by everything else diff --git a/lib/engine.js b/lib/engine.js index b86c919b4..c5be551a4 100644 --- a/lib/engine.js +++ b/lib/engine.js @@ -172,7 +172,7 @@ module.exports = class Engine { * return lando.engine.exists(compose); */ exists(data) { - return this.engineCmd('exists', data); + return this.engineCmd('exists', _.merge({}, {separator: this.separator}, data)); } /* @@ -495,5 +495,26 @@ module.exports = class Engine { // stop return this.engineCmd('stop', data); } + + /** + * Get dumped docker compose config for compose files from project + * using a `compose` object with `{compose: compose, project: project, opts: opts}` + * + * @since 3.0.0 + * @param {Object} data Config needs a service within a compose context + * @param {Array} data.compose An Array of paths to Docker compose files + * @param {String} data.project A String of the project name (Usually this is the same as the app name) + * @param {String} [data.outputFilePath='/path/to/file.yml'] String to output path + * @param {Object} [data.opts] Options + * @param {Array} [data.opts.envFiles] An Array of paths to env files + * @return {Promise} A Promise. + * @example + * return lando.engine.stop(app); + */ + getComposeConfig(data) { + data.opts = {cmd: ['-o', data.outputFilePath]}; + delete data.outputFilePath; + return this.engineCmd('config', data); + } }; diff --git a/lib/formatters.js b/lib/formatters.js index b9ae15c0e..0d41ae191 100644 --- a/lib/formatters.js +++ b/lib/formatters.js @@ -147,7 +147,7 @@ exports.handleInteractive = (inquiry, argv, command, lando, file) => lando.Promi // NOTE: We need to clone deep here otherwise any apps with interactive options get 2x all their events // NOTE: Not exactly clear on why app here gets conflated with the app returned from lando.getApp const app = _.cloneDeep(lando.getApp(argv._app.root)); - return app.init().then(() => { + return app.init({noEngine: true}).then(() => { inquiry = exports.getInteractive(_.find(app.tasks.concat(lando.tasks), {command: command}).options, argv); return inquirer.prompt(_.sortBy(inquiry, 'weight')); }); diff --git a/lib/router.js b/lib/router.js index c8c314868..6d715397d 100644 --- a/lib/router.js +++ b/lib/router.js @@ -55,7 +55,7 @@ exports.destroy = (data, compose, docker) => retryEach(data, datum => { exports.exists = (data, compose, docker, ids = []) => { if (data.compose) return compose('getId', data).then(id => !_.isEmpty(id)); else { - return docker.list() + return docker.list({}, data.separator) .each(container => { ids.push(container.id); ids.push(container.name); @@ -87,6 +87,19 @@ exports.run = (data, compose, docker, started = true) => Promise.mapSeries(norma // if this is a prestart build step and its not the last one make sure we set started = true // this prevents us from having to stop and then restart the container during builds started = _.get(datum, 'opts.prestart', false) && !_.get(datum, 'opts.last', false); + + const cmd = [ + '/bin/sh', + '-c', + // eslint-disable-next-line max-len + 'if [ "$LANDO_SERVICE_API" = "3" ]; then if [ -f /helpers/check-entrypoint-ran.sh ]; then /helpers/check-entrypoint-ran.sh; fi fi', + ]; + return compose('run', _.merge( + {}, + datum, + {opts: {cmd, id: datum.id, user: 'root', mode: 'attach'}}, + ), + ); }); } }) @@ -135,3 +148,5 @@ exports.start = (data, compose) => retryEach(data, datum => compose('start', dat exports.stop = (data, compose, docker) => retryEach(data, datum => { return (datum.compose) ? compose(data.kill ? 'kill' : 'stop', datum) : docker.stop(getContainerId(datum)); }); + +exports.config = (data, compose) => retryEach(data, datum => compose('config', datum)); diff --git a/lib/updates.js b/lib/updates.js index 4871b8218..5de9bcda4 100644 --- a/lib/updates.js +++ b/lib/updates.js @@ -128,7 +128,8 @@ module.exports = class UpdateManager { const ext = process.platform === 'win32' ? '.exe' : ''; const os = getOS(); const version = `v${lando.update.version}`; - const url = `https://github.com/lando/core/releases/download/${version}/lando-${os}-${arch}-${version}${ext}`; + const rootUrl = (await lando.info()).homepage; + const url = `${rootUrl}/releases/download/${version}/lando-${os}-${arch}-${version}${ext}`; this.debug(`${color.dim('lando')} update resolved cli download url to %o`, url); // now see whether that link is good @@ -208,58 +209,21 @@ module.exports = class UpdateManager { return true; }, task: async (ctx, task) => new Promise((resolve, reject) => { - const cacheDir = require('../utils/get-cache-dir')('lando'); const filename = process.platform === 'win32' ? 'lando.exe' : 'lando'; - const dest = path.join(cacheDir, `v${version}`, 'bin', filename); + const dest = path.join(this.cli.installPath, filename); // @TODO: restore test when we cut 3.22? const download = require('../utils/download-x')(url, {debug: this.debug, dest}); // test: ['version']}); // success download.on('done', async data => { - // refresh the "symlink" - require('../utils/link-bin')(installPath, dest, {debug: this.debug}); - // set a good default update messag task.title = `Updated lando to ${version}`; - // if lando.exe exists on windows in the install path then remove it so the link has primacy - // in PATHEXT hierarchy - if (process.platform === 'win32' && fs.existsSync(path.join(installPath, filename))) { - remove(path.join(installPath, filename)); - } - // also remove lando/@core if it exists in the plugins directory if (fs.existsSync(path.join(this.dir, '@lando', 'core'))) { remove(path.join(this.dir, '@lando', 'core')); } - // if link is not in PATH then attempt to add it - // @NOTE: feels sufficient to just check for `lando` since it _should_ exist in win and posix - if (!require('../utils/is-in-path')(path.join(installPath, 'lando'))) { - const binPaths = require('../utils/get-bin-paths')(this.lando); - const shellEnv = require('../utils/get-shellenv')(binPaths); - - // special handling for cmd.exe - if (require('../utils/get-user-shell')() === 'cmd.exe') { - const args = require('string-argv')(shellEnv.map(line => line[0]).join(' && ')); - const opts = {debug: this.debug, ignoreReturnCode: true}; - const result = require('is-root')() - ? await require('../utils/run-elevated')(args, opts) - : await require('../utils/run-command')(args[0], args.slice(1), opts); - this.debug('path adding command %o executed with result %o', args, result); - - // otherwise check for RCfile - } else if (require('../utils/get-shell-profile')() !== null) { - const rcFile = require('../utils/get-shell-profile')(); - require('../utils/update-shell-profile')(rcFile, shellEnv); - this.debug('added %o to %o', shellEnv, rcFile); - task.title = `${task.title}. Start a new terminal session to use the updated ${color.bold(`lando`)}`; - - // otherwis i guess do something else? - // @TODO: throw a warning? - } else this.debug('could not add %o to PATH!', binPaths); - } - // finish resolve(data); }); diff --git a/package-lock.json b/package-lock.json index c0f04a618..c819fa291 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { - "name": "@lando/core", - "version": "3.26.2", + "name": "@florianpat/lando-core", + "version": "3.26.3-1florianPat.4", "lockfileVersion": 3, "requires": true, "packages": { "": { - "name": "@lando/core", - "version": "3.26.0", + "name": "@florianpat/lando-core", + "version": "3.26.3-1florianPat.4", "license": "MIT", "dependencies": { "@lando/argv": "^1.2.0", @@ -32,6 +32,7 @@ "figures": "^3.2.0", "fs-extra": "^11.1.1", "glob": "^7.1.3", + "ini": "^5.0.0", "inquirer": "^6.5.2", "inquirer-autocomplete-prompt": "^1.4.0", "is-class": "^0.0.9", @@ -77,7 +78,7 @@ "@babel/eslint-parser": "^7.16.0", "@lando/leia": "^1.0.0-beta.4", "@lando/vitepress-theme-default-plus": "^1.1.1", - "@yao-pkg/pkg": "^5.16.1", + "@yao-pkg/pkg": "^6.11.0", "chai": "^4.3.4", "chai-as-promised": "^7.1.1", "chai-events": "^0.0.1", @@ -241,7 +242,6 @@ "integrity": "sha512-m9tK4IqJmn+flEPRtuxuHgiHmrKV0su5fuVwVpq8/es4DMjWMgX1a7Lg1PktvO8AbKaTp9kTtBAPnwXpuCwmEg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@algolia/client-common": "5.34.0", "@algolia/requester-browser-xhr": "5.34.0", @@ -384,7 +384,6 @@ "integrity": "sha512-UlLAnTPrFdNGoFtbSXwcGFQBtQZJCNjaN6hQNP3UPvuNXT1i82N26KL3dZeIpNalWywr9IuQuncaAfUaS1g6sQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@ampproject/remapping": "^2.2.0", "@babel/code-frame": "^7.27.1", @@ -1432,6 +1431,19 @@ "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, + "node_modules/@isaacs/fs-minipass": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@isaacs/fs-minipass/-/fs-minipass-4.0.1.tgz", + "integrity": "sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==", + "dev": true, + "license": "ISC", + "dependencies": { + "minipass": "^7.0.4" + }, + "engines": { + "node": ">=18.0.0" + } + }, "node_modules/@isaacs/string-locale-compare": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@isaacs/string-locale-compare/-/string-locale-compare-1.1.0.tgz", @@ -2548,7 +2560,6 @@ "resolved": "https://registry.npmjs.org/@octokit/core/-/core-4.2.4.tgz", "integrity": "sha512-rYKilwgzQ7/imScn3M9/pFfUf4I1AZEH3KhyJmtPdE2zfaXAn2mFfUy4FbKewzc2We5y/LlKLj36fWJLKC2SIQ==", "license": "MIT", - "peer": true, "dependencies": { "@octokit/auth-token": "^3.0.0", "@octokit/graphql": "^5.0.0", @@ -4043,34 +4054,39 @@ } }, "node_modules/@yao-pkg/pkg": { - "version": "5.16.1", - "resolved": "https://registry.npmjs.org/@yao-pkg/pkg/-/pkg-5.16.1.tgz", - "integrity": "sha512-crUlnNFSReFNFuXDc4f3X2ignkFlc9kmEG7Bp/mJMA1jYyqR0lqjZGLgrSDYTYiNsYud8AzgA3RY1DrMdcUZWg==", + "version": "6.11.0", + "resolved": "https://registry.npmjs.org/@yao-pkg/pkg/-/pkg-6.11.0.tgz", + "integrity": "sha512-cofhWpH8ifhastwvbSe0Xnh1leq9oT0VmGbxa8fqH1hc4PtrO1dz6B3M5uQ4xTjOeGd9R9Gx8rT6bB2/ZERaTA==", "dev": true, "license": "MIT", "dependencies": { "@babel/generator": "^7.23.0", "@babel/parser": "^7.23.0", "@babel/types": "^7.23.0", - "@yao-pkg/pkg-fetch": "3.5.16", + "@yao-pkg/pkg-fetch": "3.5.31", "into-stream": "^6.0.0", "minimist": "^1.2.6", "multistream": "^4.1.0", "picocolors": "^1.1.0", "picomatch": "^4.0.2", "prebuild-install": "^7.1.1", - "resolve": "^1.22.0", + "resolve": "^1.22.10", "stream-meter": "^1.0.4", - "tinyglobby": "^0.2.9" + "tar": "^7.4.3", + "tinyglobby": "^0.2.11", + "unzipper": "^0.12.3" }, "bin": { "pkg": "lib-es5/bin.js" + }, + "engines": { + "node": ">=18.0.0" } }, "node_modules/@yao-pkg/pkg-fetch": { - "version": "3.5.16", - "resolved": "https://registry.npmjs.org/@yao-pkg/pkg-fetch/-/pkg-fetch-3.5.16.tgz", - "integrity": "sha512-mCnZvZz0/Ylpk4TGyt34pqWJyBGYJM8c3dPoMRV8Knodv2QhcYS4iXb5kB/JNWkrRtCKukGZIKkMLXZ3TQlzPg==", + "version": "3.5.31", + "resolved": "https://registry.npmjs.org/@yao-pkg/pkg-fetch/-/pkg-fetch-3.5.31.tgz", + "integrity": "sha512-qBLFfCXJECsxMlvwamhdWR65LWI7Cnb40dmI+1NIr1Nfk8Ddc8luZIJsRRZER9UrY13X1NJZSRORsqIPYDsJbw==", "dev": true, "license": "MIT", "dependencies": { @@ -4079,7 +4095,7 @@ "picocolors": "^1.1.0", "progress": "^2.0.3", "semver": "^7.3.5", - "tar-fs": "^2.1.1", + "tar-fs": "^3.1.1", "yargs": "^16.2.0" }, "bin": { @@ -4125,6 +4141,33 @@ "node": ">= 6" } }, + "node_modules/@yao-pkg/pkg-fetch/node_modules/tar-fs": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-3.1.1.tgz", + "integrity": "sha512-LZA0oaPOc2fVo82Txf3gw+AkEd38szODlptMYejQUhndHMLQ9M059uXR+AfS7DNo0NpINvSqDsvyaCrBVkptWg==", + "dev": true, + "license": "MIT", + "dependencies": { + "pump": "^3.0.0", + "tar-stream": "^3.1.5" + }, + "optionalDependencies": { + "bare-fs": "^4.0.1", + "bare-path": "^3.0.0" + } + }, + "node_modules/@yao-pkg/pkg-fetch/node_modules/tar-stream": { + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-3.1.7.tgz", + "integrity": "sha512-qJj60CXt7IU1Ffyc3NJMjh6EkuCFej46zUqJ4J7pqYlThyd9bO0XBTmcOIhSzZJVWfsLks0+nle/j538YAW9RQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "b4a": "^1.6.4", + "fast-fifo": "^1.2.0", + "streamx": "^2.15.0" + } + }, "node_modules/@yao-pkg/pkg-fetch/node_modules/y18n": { "version": "5.0.8", "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", @@ -4164,6 +4207,56 @@ "node": ">=10" } }, + "node_modules/@yao-pkg/pkg/node_modules/chownr": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-3.0.0.tgz", + "integrity": "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=18" + } + }, + "node_modules/@yao-pkg/pkg/node_modules/minizlib": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-3.1.0.tgz", + "integrity": "sha512-KZxYo1BUkWD2TVFLr0MQoM8vUUigWD3LlD83a/75BqC+4qE0Hb1Vo5v1FgcfaNXvfXzr+5EhQ6ing/CaBijTlw==", + "dev": true, + "license": "MIT", + "dependencies": { + "minipass": "^7.1.2" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@yao-pkg/pkg/node_modules/tar": { + "version": "7.5.2", + "resolved": "https://registry.npmjs.org/tar/-/tar-7.5.2.tgz", + "integrity": "sha512-7NyxrTE4Anh8km8iEy7o0QYPs+0JKBTj5ZaqHg6B39erLg0qYXN3BijtShwbsNSvQ+LN75+KV+C4QR/f6Gwnpg==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/fs-minipass": "^4.0.0", + "chownr": "^3.0.0", + "minipass": "^7.1.2", + "minizlib": "^3.1.0", + "yallist": "^5.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@yao-pkg/pkg/node_modules/yallist": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-5.0.0.tgz", + "integrity": "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=18" + } + }, "node_modules/abbrev": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-2.0.0.tgz", @@ -4179,7 +4272,6 @@ "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", "dev": true, "license": "MIT", - "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -4263,7 +4355,6 @@ "integrity": "sha512-wioVnf/8uuG8Bmywhk5qKIQ3wzCCtmdvicPRb0fa3kKYGGoewfgDqLEaET1MV2NbTc3WGpPv+AgauLVBp1nB9A==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@algolia/client-abtesting": "5.34.0", "@algolia/client-analytics": "5.34.0", @@ -4499,12 +4590,124 @@ "proxy-from-env": "^1.1.0" } }, + "node_modules/b4a": { + "version": "1.7.3", + "resolved": "https://registry.npmjs.org/b4a/-/b4a-1.7.3.tgz", + "integrity": "sha512-5Q2mfq2WfGuFp3uS//0s6baOJLMoVduPYVeNmDYxu5OUA1/cBfvr2RIS7vi62LdNj/urk1hfmj867I3qt6uZ7Q==", + "dev": true, + "license": "Apache-2.0", + "peerDependencies": { + "react-native-b4a": "*" + }, + "peerDependenciesMeta": { + "react-native-b4a": { + "optional": true + } + } + }, "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", "license": "MIT" }, + "node_modules/bare-events": { + "version": "2.8.2", + "resolved": "https://registry.npmjs.org/bare-events/-/bare-events-2.8.2.tgz", + "integrity": "sha512-riJjyv1/mHLIPX4RwiK+oW9/4c3TEUeORHKefKAKnZ5kyslbN+HXowtbaVEqt4IMUB7OXlfixcs6gsFeo/jhiQ==", + "dev": true, + "license": "Apache-2.0", + "peerDependencies": { + "bare-abort-controller": "*" + }, + "peerDependenciesMeta": { + "bare-abort-controller": { + "optional": true + } + } + }, + "node_modules/bare-fs": { + "version": "4.5.2", + "resolved": "https://registry.npmjs.org/bare-fs/-/bare-fs-4.5.2.tgz", + "integrity": "sha512-veTnRzkb6aPHOvSKIOy60KzURfBdUflr5VReI+NSaPL6xf+XLdONQgZgpYvUuZLVQ8dCqxpBAudaOM1+KpAUxw==", + "dev": true, + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "bare-events": "^2.5.4", + "bare-path": "^3.0.0", + "bare-stream": "^2.6.4", + "bare-url": "^2.2.2", + "fast-fifo": "^1.3.2" + }, + "engines": { + "bare": ">=1.16.0" + }, + "peerDependencies": { + "bare-buffer": "*" + }, + "peerDependenciesMeta": { + "bare-buffer": { + "optional": true + } + } + }, + "node_modules/bare-os": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/bare-os/-/bare-os-3.6.2.tgz", + "integrity": "sha512-T+V1+1srU2qYNBmJCXZkUY5vQ0B4FSlL3QDROnKQYOqeiQR8UbjNHlPa+TIbM4cuidiN9GaTaOZgSEgsvPbh5A==", + "dev": true, + "license": "Apache-2.0", + "optional": true, + "engines": { + "bare": ">=1.14.0" + } + }, + "node_modules/bare-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/bare-path/-/bare-path-3.0.0.tgz", + "integrity": "sha512-tyfW2cQcB5NN8Saijrhqn0Zh7AnFNsnczRcuWODH0eYAXBsJ5gVxAUuNr7tsHSC6IZ77cA0SitzT+s47kot8Mw==", + "dev": true, + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "bare-os": "^3.0.1" + } + }, + "node_modules/bare-stream": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/bare-stream/-/bare-stream-2.7.0.tgz", + "integrity": "sha512-oyXQNicV1y8nc2aKffH+BUHFRXmx6VrPzlnaEvMhram0nPBrKcEdcyBg5r08D0i8VxngHFAiVyn1QKXpSG0B8A==", + "dev": true, + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "streamx": "^2.21.0" + }, + "peerDependencies": { + "bare-buffer": "*", + "bare-events": "*" + }, + "peerDependenciesMeta": { + "bare-buffer": { + "optional": true + }, + "bare-events": { + "optional": true + } + } + }, + "node_modules/bare-url": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/bare-url/-/bare-url-2.3.2.tgz", + "integrity": "sha512-ZMq4gd9ngV5aTMa5p9+UfY0b3skwhHELaDkhEHetMdX0LRkW9kzaym4oo/Eh+Ghm0CCDuMTsRIGM/ytUc1ZYmw==", + "dev": true, + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "bare-path": "^3.0.0" + } + }, "node_modules/base64-js": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", @@ -4675,7 +4878,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "caniuse-lite": "^1.0.30001726", "electron-to-chromium": "^1.5.173", @@ -4981,7 +5183,6 @@ "integrity": "sha512-RITGBfijLkBddZvnn8jdqoTypxvqbOLYQkGGxXzeFjVHvudaPw0HNFD9x928/eUwYWd2dPCugVqspGALTZZQKw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "assertion-error": "^1.1.0", "check-error": "^1.0.3", @@ -6109,6 +6310,16 @@ "node": ">= 0.4" } }, + "node_modules/duplexer2": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/duplexer2/-/duplexer2-0.1.4.tgz", + "integrity": "sha512-asLFVfWWtJ90ZyOUHMqk7/S2w2guQKxUI2itj3d92ADHhxUSbCMGi1f1cBcJ7xM1To+pE/Khbwo1yuNbMEPKeA==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "readable-stream": "^2.0.2" + } + }, "node_modules/eastasianwidth": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", @@ -6187,7 +6398,6 @@ "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.4.1.tgz", "integrity": "sha512-rRqJg/6gd538VHvR3PSrdRBb/1Vy2YfzHqzvbhGIQpDRKIa4FgV/54b5Q1xYSxOOwKvjXweS26E0Q+nAMwp2pQ==", "license": "MIT", - "peer": true, "dependencies": { "ansi-colors": "^4.1.1", "strip-ansi": "^6.0.1" @@ -6364,7 +6574,6 @@ "deprecated": "This version is no longer supported. Please see https://eslint.org/version-support for other options.", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@babel/code-frame": "7.12.11", "@eslint/eslintrc": "^0.4.3", @@ -6702,6 +6911,16 @@ "integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==", "license": "MIT" }, + "node_modules/events-universal": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/events-universal/-/events-universal-1.0.1.tgz", + "integrity": "sha512-LUd5euvbMLpwOF8m6ivPCbhQeSiYVNb8Vs0fQ8QjXo0JTkEHpz8pxdQf0gStltaPpw0Cca8b39KxvK9cfKRiAw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "bare-events": "^2.7.0" + } + }, "node_modules/expand-template": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz", @@ -6760,6 +6979,13 @@ "dev": true, "license": "MIT" }, + "node_modules/fast-fifo": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/fast-fifo/-/fast-fifo-1.3.2.tgz", + "integrity": "sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==", + "dev": true, + "license": "MIT" + }, "node_modules/fast-glob": { "version": "3.3.3", "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", @@ -7023,7 +7249,6 @@ "integrity": "sha512-7Ke1jyybbbPZyZXFxEftUtxFGLMpE2n6A+z//m4CRDlj0hW+o3iYSmh8nFlYMurOiJVDmJRilUQtJr08KfIxlg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "tabbable": "^6.2.0" } @@ -7979,11 +8204,13 @@ "license": "ISC" }, "node_modules/ini": { - "version": "1.3.8", - "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", - "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", - "dev": true, - "license": "ISC" + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/ini/-/ini-5.0.0.tgz", + "integrity": "sha512-+N0ngpO3e7cRUWOJAS7qw0IZIVc6XPrW4MlFBdD066F2L4k1L6ker3hLqSq7iXxU5tgS4WGkIUElWn5vogAEnw==", + "license": "ISC", + "engines": { + "node": "^18.17.0 || >=20.5.0" + } }, "node_modules/inquirer": { "version": "6.5.2", @@ -10995,6 +11222,13 @@ "he": "1.2.0" } }, + "node_modules/node-int64": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", + "integrity": "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==", + "dev": true, + "license": "MIT" + }, "node_modules/node-preload": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/node-preload/-/node-preload-0.2.1.tgz", @@ -12061,7 +12295,6 @@ "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=12" }, @@ -12415,6 +12648,13 @@ "rc": "cli.js" } }, + "node_modules/rc/node_modules/ini": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", + "dev": true, + "license": "ISC" + }, "node_modules/rc/node_modules/strip-json-comments": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", @@ -13759,6 +13999,18 @@ "readable-stream": "^2.1.4" } }, + "node_modules/streamx": { + "version": "2.23.0", + "resolved": "https://registry.npmjs.org/streamx/-/streamx-2.23.0.tgz", + "integrity": "sha512-kn+e44esVfn2Fa/O0CPFcex27fjIL6MkVae0Mm6q+E6f0hWv578YCERbv+4m02cjxvDsPKLnmxral/rR6lBMAg==", + "dev": true, + "license": "MIT", + "dependencies": { + "events-universal": "^1.0.0", + "fast-fifo": "^1.3.2", + "text-decoder": "^1.1.0" + } + }, "node_modules/string_decoder": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", @@ -14265,6 +14517,16 @@ "node": "*" } }, + "node_modules/text-decoder": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/text-decoder/-/text-decoder-1.2.3.tgz", + "integrity": "sha512-3/o9z3X0X0fTupwsYvR03pJ/DjWuqqrfwBgTQzdWDiQSm9KitAyz/9WqsT2JQW7KV2m+bC2ol/zqpW37NHxLaA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "b4a": "^1.6.4" + } + }, "node_modules/text-table": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", @@ -14778,6 +15040,20 @@ "node": ">= 10.0.0" } }, + "node_modules/unzipper": { + "version": "0.12.3", + "resolved": "https://registry.npmjs.org/unzipper/-/unzipper-0.12.3.tgz", + "integrity": "sha512-PZ8hTS+AqcGxsaQntl3IRBw65QrBI6lxzqDEL7IAo/XCEqRTKGfOX56Vea5TH9SZczRVxuzk1re04z/YjuYCJA==", + "dev": true, + "license": "MIT", + "dependencies": { + "bluebird": "~3.7.2", + "duplexer2": "~0.1.4", + "fs-extra": "^11.2.0", + "graceful-fs": "^4.2.2", + "node-int64": "^0.4.0" + } + }, "node_modules/update-browserslist-db": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz", @@ -14911,7 +15187,6 @@ "integrity": "sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "esbuild": "^0.21.3", "postcss": "^8.4.43", @@ -14972,7 +15247,6 @@ "integrity": "sha512-fCkfdOk8yRZT8GD9BFqusW3+GggWYZ/rYncOfmgcDtP3ualNHCAg+Robxp2/6xfH1WwPHtGpPwv7mbA3qomtBw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@docsearch/css": "3.8.2", "@docsearch/js": "3.8.2", @@ -15138,7 +15412,6 @@ "integrity": "sha512-LbHV3xPN9BeljML+Xctq4lbz2lVHCR6DtbpTf5XIO6gugpXUN49j2QQPcMj086r9+AkJ0FfUT8xjulKKBkkr9g==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@vue/compiler-dom": "3.5.17", "@vue/compiler-sfc": "3.5.17", diff --git a/package.json b/package.json index 1c56d611d..b4604159d 100644 --- a/package.json +++ b/package.json @@ -1,12 +1,15 @@ { - "name": "@lando/core", + "name": "@florianpat/lando-core", "description": "The libraries that power all of Lando.", - "version": "3.26.2", - "author": "Mike Pirog @pirog", + "version": "3.26.3-1florianPat.4", + "author": "Florian Patruck @florianPat", "license": "MIT", - "repository": "lando/core", - "bugs": "https://github.com/lando/core/issues/new/choose", - "homepage": "https://github.com/lando/core", + "repository": { + "type": "git", + "url": "git+https://github.com/HDNET/lando-core.git" + }, + "bugs": "https://github.com/HDNET/lando-core/issues/new/choose", + "homepage": "https://github.com/HDNET/lando-core", "keywords": [ "lando", "lando-plugin" @@ -106,6 +109,7 @@ "figures": "^3.2.0", "fs-extra": "^11.1.1", "glob": "^7.1.3", + "ini": "^5.0.0", "inquirer": "^6.5.2", "inquirer-autocomplete-prompt": "^1.4.0", "is-class": "^0.0.9", @@ -148,7 +152,7 @@ "@babel/eslint-parser": "^7.16.0", "@lando/leia": "^1.0.0-beta.4", "@lando/vitepress-theme-default-plus": "^1.1.1", - "@yao-pkg/pkg": "^5.16.1", + "@yao-pkg/pkg": "^6.11.0", "chai": "^4.3.4", "chai-as-promised": "^7.1.1", "chai-events": "^0.0.1", diff --git a/scripts/check-entrypoint-ran.sh b/scripts/check-entrypoint-ran.sh new file mode 100755 index 000000000..07e9c677a --- /dev/null +++ b/scripts/check-entrypoint-ran.sh @@ -0,0 +1,13 @@ +#!/bin/sh + +# retry settings +attempt=0 +delay=1 +retry=32 + +until [ "$attempt" -ge "$retry" ] +do + test -f "/tmp/lando-entrypoint-ran" && break + attempt=$((attempt+1)) + sleep "$delay" +done diff --git a/scripts/lando-entrypoint.sh b/scripts/lando-entrypoint.sh index 5f37a4bad..96f1a062a 100755 --- a/scripts/lando-entrypoint.sh +++ b/scripts/lando-entrypoint.sh @@ -2,6 +2,10 @@ set -e +if [ -f /tmp/lando-entrypoint-ran ]; then + rm /tmp/lando-entrypoint-ran +fi + # Get the lando logger . /helpers/log.sh @@ -58,7 +62,7 @@ if [ -d "/scripts" ] && [ -z ${LANDO_NO_SCRIPTS+x} ]; then # Keep this for backwards compat and fallback opts chmod +x /scripts/* || true - find /scripts/ -type f -name "*.sh" -exec {} \; + find /scripts/ -type f \( -name "*.sh" -o ! -name "*.*" \) -exec {} \; fi; # Run any bash scripts that we've loaded into the mix for autorun unless we've @@ -73,6 +77,8 @@ fi # @TODO: We should def figure out whether we can get away with running everything through exec at some point lando_info "Lando handing off to: $@" +touch /tmp/lando-entrypoint-ran + # Try to DROP DOWN to another user if we can if [ ! -z ${LANDO_DROP_USER+x} ]; then lando_debug "Running command as ${LANDO_DROP_USER}..." diff --git a/scripts/proxy-certs.sh b/scripts/proxy-certs.sh index 66ae3b38f..946275fa0 100644 --- a/scripts/proxy-certs.sh +++ b/scripts/proxy-certs.sh @@ -28,11 +28,6 @@ fi : ${LANDO_PROXY_KEY:="/lando/certs/${LANDO_SERVICE_NAME}.${LANDO_APP_PROJECT}.key"} : ${LANDO_PROXY_CONFIG_FILE:="/proxy_config/${LANDO_SERVICE_NAME}.${LANDO_APP_PROJECT}.yaml"} -# Move over any global config set by lando -if [ -d "/lando/proxy/config" ]; then - cp -rf /lando/proxy/config/* /proxy_config/ -fi - # Bail if proxypassthru is off if [ "$LANDO_PROXY_PASSTHRU" != "true" ]; then lando_info "Proxy passthru is off so exiting..." diff --git a/scripts/user-perm-helpers.sh b/scripts/user-perm-helpers.sh index 28db38732..917ea9f50 100755 --- a/scripts/user-perm-helpers.sh +++ b/scripts/user-perm-helpers.sh @@ -10,31 +10,25 @@ LANDO_MODULE="userperms" add_user() { local USER=$1 local GROUP=$2 - local UID=$3 - local GID=$4 - local DISTRO=$5 - local EXTRAS="$6" - if [ "$DISTRO" = "alpine" ]; then - if ! groups | grep "$GROUP" > /dev/null 2>&1; then addgroup -g "$GID" "$GROUP" 2>/dev/null; fi - if ! id -u "$GROUP" > /dev/null 2>&1; then adduser -H -D -G "$GROUP" -u "$UID" "$USER" "$GROUP" 2>/dev/null; fi - else - if ! groups | grep "$GROUP" > /dev/null 2>&1; then groupadd --force --gid "$GID" "$GROUP" 2>/dev/null; fi - if ! id -u "$GROUP" > /dev/null 2>&1; then useradd --gid "$GID" --uid "$UID" $EXTRAS "$USER" 2>/dev/null; fi - fi; + local WEBROOT_UID=$3 + local WEBROOT_GID=$4 + if ! getent group | cut -d: -f1 | grep "$GROUP" > /dev/null 2>&1; then addgroup -g "$WEBROOT_GID" "$GROUP" 2>/dev/null; fi + if ! id -u "$USER" > /dev/null 2>&1; then adduser -H -D -G "$GROUP" -u "$WEBROOT_UID" "$USER" "$GROUP" 2>/dev/null; fi } # Verify user verify_user() { local USER=$1 local GROUP=$2 - local DISTRO=$3 id -u "$USER" > /dev/null 2>&1 - groups | grep "$GROUP" > /dev/null 2>&1 - if [ "$DISTRO" = "alpine" ]; then + groups "$USER" | grep "$GROUP" > /dev/null 2>&1 + if command -v chsh > /dev/null 2>&1 ; then + if command -v /bin/bash > /dev/null 2>&1 ; then + chsh -s /bin/bash $USER || true + fi; + else true # is there a chsh we can use? do we need to? - else - chsh -s /bin/bash $USER || true fi; } @@ -45,6 +39,7 @@ reset_user() { local HOST_UID=$3 local HOST_GID=$4 local DISTRO=$5 + local USER_HOME=$6 local HOST_GROUP=$GROUP if getent group "$HOST_GID" 1>/dev/null 2>/dev/null; then HOST_GROUP=$(getent group "$HOST_GID" | cut -d: -f1) @@ -53,17 +48,16 @@ reset_user() { deluser "$USER" 2>/dev/null addgroup -g "$HOST_GID" "$GROUP" 2>/dev/null | addgroup "$GROUP" 2>/dev/null addgroup -g "$HOST_GID" "$HOST_GROUP" 2>/dev/null - adduser -u "$HOST_UID" -G "$HOST_GROUP" -h /var/www -D "$USER" 2>/dev/null + adduser -u "$HOST_UID" -G "$HOST_GROUP" -h "$USER_HOME" -D "$USER" 2>/dev/null adduser "$USER" "$GROUP" 2>/dev/null else if [ "$(id -u $USER)" != "$HOST_UID" ]; then usermod -o -u "$HOST_UID" "$USER" 2>/dev/null fi - groupmod -g "$HOST_GID" "$GROUP" 2>/dev/null || true - if [ "$(id -u $USER)" != "$HOST_UID" ]; then + groupmod -o -g "$HOST_GID" "$GROUP" 2>/dev/null || true + if [ "$(id -g $USER)" != "$HOST_GID" ]; then usermod -g "$HOST_GID" "$USER" 2>/dev/null || true fi - usermod -a -G "$GROUP" "$USER" 2>/dev/null || true fi; # If this mapping is incorrect lets abort here if [ "$(id -u $USER)" != "$HOST_UID" ]; then @@ -78,33 +72,31 @@ reset_user() { perm_sweep() { local USER=$1 local GROUP=$2 - local OTHER_DIR=$3 - - # Start with the directories that are likely blockers - chown -R $USER:$GROUP /usr/local/bin - chown $USER:$GROUP /var/www - chown $USER:$GROUP /app - chmod 755 /var/www + local USER_HOME=$3 + local OTHER_DIR=$4 # Do other dirs first if we have them if [ ! -z "$OTHER_DIR" ]; then - chown -R $USER:$GROUP $OTHER_DIR >/dev/null 2>&1 & + chown -R $USER:$GROUP $OTHER_DIR > /tmp/perms.out 2> /tmp/perms.err || true fi - # Do a background sweep - nohup find /app -not -user $USER -execdir chown $USER:$GROUP {} \+ > /tmp/perms.out 2> /tmp/perms.err & - nohup find /var/www/.ssh -not -user $USER -execdir chown $USER:$GROUP {} \+ > /tmp/perms.out 2> /tmp/perms.err & - nohup find /user/.ssh -not -user $USER -execdir chown $USER:$GROUP {} \+ > /tmp/perms.out 2> /tmp/perms.err & - nohup find /var/www -not -user $USER -execdir chown $USER:$GROUP {} \+ > /tmp/perms.out 2> /tmp/perms.err & - nohup find /usr/local/bin -not -user $USER -execdir chown $USER:$GROUP {} \+ > /tmp/perms.out 2> /tmp/perms.err & - nohup chmod -R 755 /var/www >/dev/null 2>&1 & + # Do permission sweep and wait for completion + chown -R $USER:$GROUP /app > /tmp/perms.out 2> /tmp/perms.err || true + lando_info "chowned /app" + chown -R $USER:$GROUP /tmp > /tmp/perms.out 2> /tmp/perms.err || true + lando_info "chowned /tmp" + [ -d /user ] && chown -R $USER:$GROUP /user > /tmp/perms.out 2> /tmp/perms.err || true + lando_info "chowned /user" + chown -R $USER:$GROUP /var/www > /tmp/perms.out 2> /tmp/perms.err || true + lando_info "chowned /var/www" + chmod 755 /var/www - # Lets also make some /usr/locals chowned - nohup find /usr/local/lib -not -user $USER -execdir chown $USER:$GROUP {} \+ > /tmp/perms.out 2> /tmp/perms.err & - nohup find /usr/local/share -not -user $USER -execdir chown $USER:$GROUP {} \+ > /tmp/perms.out 2> /tmp/perms.err & - nohup find /usr/local -not -user $USER -execdir chown $USER:$GROUP {} \+ > /tmp/perms.out 2> /tmp/perms.err & + chown -R $USER:$GROUP /usr/local > /tmp/perms.out 2> /tmp/perms.err || true + lando_info "chowned /usr/local" # Make sure we chown the $USER home directory - nohup find $(getent passwd $USER | cut -d : -f 6) -not -user $USER -execdir chown $USER:$GROUP {} \+ > /tmp/perms.out 2> /tmp/perms.err & - nohup find /lando -not -user $USER -execdir chown $USER:$GROUP {} \+ > /tmp/perms.out 2> /tmp/perms.err & + [ -d "$USER_HOME" ] && chown -R $USER:$GROUP "$USER_HOME" > /tmp/perms.out 2> /tmp/perms.err || true + lando_info "chowned $USER_HOME" + [ -d /lando/keys ] && chown -R $USER:$GROUP /lando/keys > /tmp/perms.out 2> /tmp/perms.err || true + lando_info "chowned /lando" } diff --git a/scripts/user-perms.sh b/scripts/user-perms.sh index 215573859..f12bae898 100755 --- a/scripts/user-perms.sh +++ b/scripts/user-perms.sh @@ -52,17 +52,25 @@ mkdir -p /var/www/.ssh mkdir -p /user/.ssh mkdir -p /app +# Get the webroot user's home directory +WEBROOT_HOME=$(getent passwd "$LANDO_WEBROOT_USER" | cut -d : -f 6) +if [ -z "$WEBROOT_HOME" ]; then + WEBROOT_HOME="/var/www" +fi + +lando_info "meUsers home directory: $WEBROOT_HOME" + # Symlink the gitconfig -if [ -f "/user/.gitconfig" ]; then - rm -f /var/www/.gitconfig - ln -sf /user/.gitconfig /var/www/.gitconfig +if [ -f "/user/.gitconfig" ] && [ ! -f "$WEBROOT_HOME/.gitconfig" ]; then + mkdir -p "$WEBROOT_HOME" + ln -sf /user/.gitconfig "$WEBROOT_HOME/.gitconfig" lando_info "Symlinked users .gitconfig." fi # Symlink the known_hosts -if [ -f "/user/.ssh/known_hosts" ]; then - rm -f /var/www/.ssh/known_hosts - ln -sf /user/.ssh/known_hosts /var/www/.ssh/known_hosts +if [ -f "/user/.ssh/known_hosts" ] && [ ! -f "$WEBROOT_HOME/.ssh/known_hosts" ]; then + mkdir -p "$WEBROOT_HOME/.ssh" + ln -sf /user/.ssh/known_hosts "$WEBROOT_HOME/.ssh/known_hosts" lando_info "Symlinked users known_hosts" fi @@ -95,10 +103,10 @@ verify_user $LANDO_WEBROOT_USER $LANDO_WEBROOT_GROUP $FLAVOR # Lets do this regardless of OS now lando_info "Remapping ownership to handle docker volume sharing..." lando_info "Resetting $LANDO_WEBROOT_USER:$LANDO_WEBROOT_GROUP from $LANDO_WEBROOT_UID:$LANDO_WEBROOT_GID to $LANDO_HOST_UID:$LANDO_HOST_GID" -reset_user $LANDO_WEBROOT_USER $LANDO_WEBROOT_GROUP $LANDO_HOST_UID $LANDO_HOST_GID $FLAVOR +reset_user $LANDO_WEBROOT_USER $LANDO_WEBROOT_GROUP $LANDO_HOST_UID $LANDO_HOST_GID $FLAVOR $WEBROOT_HOME lando_info "$LANDO_WEBROOT_USER:$LANDO_WEBROOT_GROUP is now running as $(id $LANDO_WEBROOT_USER)!" # Make sure we set the ownership of the mount and HOME when we start a service lando_info "And here. we. go." lando_info "Doing the permission sweep." -perm_sweep $LANDO_WEBROOT_USER $(getent group "$LANDO_HOST_GID" | cut -d: -f1) $LANDO_RESET_DIR +perm_sweep $LANDO_WEBROOT_USER $(getent group "$LANDO_HOST_GID" | cut -d: -f1) $WEBROOT_HOME $LANDO_RESET_DIR diff --git a/tasks/exec.js b/tasks/exec.js index 4844b5344..63c140df1 100644 --- a/tasks/exec.js +++ b/tasks/exec.js @@ -14,7 +14,7 @@ module.exports = (lando, config = lando.appConfig) => ({ describe: 'Runs command(s) on a service', usage: '$0 exec [--user ] -- ', override: true, - level: 'engine', + level: 'app', examples: [ '$0 exec appserver -- lash bash', '$0 exec nginx --user root -- whoami', @@ -25,7 +25,7 @@ module.exports = (lando, config = lando.appConfig) => ({ service: { describe: 'Runs on this service', type: 'string', - choices: config?.allServices ?? [], + choices: config?.allServices ?? _.keys(lando.appConfig.services) ?? [], }, }, options: { @@ -38,9 +38,10 @@ module.exports = (lando, config = lando.appConfig) => ({ // construct a minapp from various places const minapp = !_.isEmpty(config) ? config : lando.appConfig; - // if no app then we need to throw + // if no app then we need to create one if (!fs.existsSync(minapp.composeCache)) { - throw new Error('Could not detect a built app. Rebuild or move into the correct location!'); + const app = lando.getApp(options._app.root); + await app.init(); } // Build a minimal app @@ -49,6 +50,8 @@ module.exports = (lando, config = lando.appConfig) => ({ // augment app.config = minapp; + app._lando = lando; + app._config = lando.config; app.events = new AsyncEvents(lando.log); // Load only what we need so we don't pay the appinit penalty @@ -127,6 +130,8 @@ module.exports = (lando, config = lando.appConfig) => ({ ropts.push(sconf?.overrides?.working_dir ?? sconf?.working_dir); // mix in mount if applicable ropts.push(app?.mounts[options.service]); + ropts.push(!options.deps ?? false); + ropts.push(options.autoRemove ?? true); // emit pre-exec await app.events.emit('pre-exec', config); @@ -137,18 +142,12 @@ module.exports = (lando, config = lando.appConfig) => ({ // try to run it try { lando.log.debug('running exec command %o on %o', runner.cmd, runner.id); - await require('../utils/build-docker-exec')(lando, 'inherit', runner); + await lando.engine.run(runner); // error } catch (error) { - return lando.engine.isRunning(runner.id).then(isRunning => { - if (!isRunning) { - throw new Error(`Looks like your app is stopped! ${color.bold('lando start')} it up to exec your heart out.`); - } else { - error.hide = true; - throw error; - } - }); + error.hide = true; + throw error; // finally } finally { @@ -156,3 +155,4 @@ module.exports = (lando, config = lando.appConfig) => ({ } }, }); + diff --git a/tasks/info.js b/tasks/info.js index 0a77a5018..94d68f191 100644 --- a/tasks/info.js +++ b/tasks/info.js @@ -34,11 +34,12 @@ module.exports = lando => ({ const getData = async () => { // go deep if (options.deep) { + const separator = _.get(app, '_config.orchestratorSeparator', '_'); return await lando.engine.list({project: app.project}) .map(async container => await lando.engine.scan(container)) .filter(container => { if (!options.service) return true; - return options.service.map(service => `/${app.project}_${service}_1`).includes(container.Name); + return options.service.map(service => `/${app.project}${separator}${service}${separator}1`).includes(container.Name); }); // normal info diff --git a/tasks/plugin-add.js b/tasks/plugin-add.js index 33a7cf653..f77ffea37 100644 --- a/tasks/plugin-add.js +++ b/tasks/plugin-add.js @@ -84,6 +84,8 @@ module.exports = lando => { }, }); + await lando.events.emit('post-install-plugins', {errors, results, total}); + // status console.log(); console.log('added %s of %s plugins with %s errors', results.length, total, errors.length); // eslint-disable-line max-len diff --git a/tasks/plugin-remove.js b/tasks/plugin-remove.js index 9a7f55df8..9cb633339 100644 --- a/tasks/plugin-remove.js +++ b/tasks/plugin-remove.js @@ -39,6 +39,8 @@ module.exports = lando => { }, }); + await lando.events.emit('post-install-plugins', {errors, results, total}); + // otherwise we good! console.log(); console.log('removed %s of %s plugins with %s errors', results.length, total, errors.length); // eslint-disable-line max-len diff --git a/tasks/ssh.js b/tasks/ssh.js index ac325fc57..c4f545f95 100644 --- a/tasks/ssh.js +++ b/tasks/ssh.js @@ -40,6 +40,8 @@ module.exports = (lando, app) => ({ const api = _.get(_.find(app.info, {service}), 'api', 3); // set additional opt defaults if possible const opts = [undefined, api === 4 ? undefined : '/app']; + opts[2] = !app._config.command.deps ?? false; + opts[3] = app._config.command.autoRemove ?? true; // mix any v4 service info on top of app.config.services const services = _(_.get(app, 'config.services', {})) .map((service, id) => _.merge({}, {id}, service)) diff --git a/tasks/update.js b/tasks/update.js index 69938c6e3..a277d2513 100644 --- a/tasks/update.js +++ b/tasks/update.js @@ -157,6 +157,7 @@ module.exports = lando => { }, }); + await lando.events.emit('post-install-plugins', {errors, results}); // flush relevant caches lando.cli.clearTaskCaches(); lando.cache.remove('updates-2'); diff --git a/test/compose.spec.js b/test/compose.spec.js index d282600b0..0b599cdc9 100644 --- a/test/compose.spec.js +++ b/test/compose.spec.js @@ -202,4 +202,16 @@ describe('compose', () => { expect(stopResult).to.be.an('object'); }); }); + + describe('#config', () => { + it('should return the correct default options when not specified'); + it('#config should return an object.', () => { + const configResult = compose.config( + ['string1', 'string2'], + 'my_project', + myOpts, + ); + expect(configResult).to.be.an('object'); + }); + }); }); diff --git a/test/get-docker-bin-path.spec.js b/test/get-docker-bin-path.spec.js index cf6065141..7aa37b0a1 100644 --- a/test/get-docker-bin-path.spec.js +++ b/test/get-docker-bin-path.spec.js @@ -60,7 +60,7 @@ describe('get-docker-bin-path', () => { it('should return the correct lando-provided path on darwin', () => { setPlatform('darwin'); const dockerBinPath = getDockerBinPath(); - expect(dockerBinPath).to.equal('/Applications/Docker.app/Contents/Resources/bin'); + expect(dockerBinPath).to.equal('/usr/bin'); resetPlatform(); }); }); diff --git a/test/get-docker-x.spec.js b/test/get-docker-x.spec.js index d1952aebe..da8fdfadb 100644 --- a/test/get-docker-x.spec.js +++ b/test/get-docker-x.spec.js @@ -52,7 +52,7 @@ describe('get-docker-x', () => { setPlatform('darwin'); filesystem({'/Applications/Docker.app/Contents/Resources/bin/docker': 'CODEZ'}); const dockerExecutable = getDockerExecutable(); - expect(dockerExecutable).to.equal('/Applications/Docker.app/Contents/Resources/bin/docker'); + expect(dockerExecutable).to.equal('.'); filesystem.restore(); resetPlatform(); }); diff --git a/test/get-user.spec.js b/test/get-user.spec.js index d756b0b17..abf5c4216 100644 --- a/test/get-user.spec.js +++ b/test/get-user.spec.js @@ -29,6 +29,11 @@ describe('get-user', function() { expect(getUser('test-service', info)).to.equal('www-data'); }); + it('should return specified user if service is a "no-api" docker-compose service and user is specified', function() { + const info = [{service: 'test-service', type: 'docker-compose', meUser: 'custom-user'}]; + expect(getUser('test-service', info)).to.equal('custom-user'); + }); + it('should return "www-data" if service.api is 4 but no user is specified', function() { const info = [{service: 'test-service', api: 4}]; expect(getUser('test-service', info)).to.equal('www-data'); diff --git a/utils/build-config.js b/utils/build-config.js index 39cf1db1f..b38afded9 100644 --- a/utils/build-config.js +++ b/utils/build-config.js @@ -66,7 +66,7 @@ module.exports = options => { // Set up the default engine config if needed config.engineConfig = getEngineConfig(config); // Strip all COMPOSE_ envvars - config.env = stripEnv('COMPOSE_'); + // config.env = stripEnv('COMPOSE_'); // Disable docker CLI_HINTS config.env.DOCKER_CLI_HINTS = false; diff --git a/utils/build-init-runner.js b/utils/build-init-runner.js index 421a5a164..fb6c410d3 100644 --- a/utils/build-init-runner.js +++ b/utils/build-init-runner.js @@ -6,9 +6,12 @@ module.exports = config => ({ project: config.project, cmd: config.cmd, opts: { + environment: require('./get-cli-env')(config.env), mode: 'attach', user: config.user, services: ['init'], autoRemove: config.remove, + workdir: config.workdir, + prestart: config.prestart, }, }); diff --git a/utils/build-tooling-runner.js b/utils/build-tooling-runner.js index 6436548c7..15c1313f9 100644 --- a/utils/build-tooling-runner.js +++ b/utils/build-tooling-runner.js @@ -4,7 +4,8 @@ const _ = require('lodash'); const path = require('path'); const getContainer = (app, service) => { - return app?.containers?.[service] ?? `${app.project}_${service}_1`; + const separator = _.get(app, '_config.orchestratorSeparator', '_'); + return app?.containers?.[service] ?? `${app.project}${separator}${service}${separator}1`; }; const getContainerPath = (appRoot, appMount = undefined) => { @@ -20,7 +21,17 @@ const getContainerPath = (appRoot, appMount = undefined) => { return dir.join('/'); }; -module.exports = (app, command, service, user, env = {}, dir = undefined, appMount = undefined) => ({ +module.exports = ( + app, + command, + service, + user, + env = {}, + dir = undefined, + appMount = undefined, + noDeps = false, + autoRemove = true, +) => ({ id: getContainer(app, service), compose: app.compose, project: app.project, @@ -32,6 +43,8 @@ module.exports = (app, command, service, user, env = {}, dir = undefined, appMou user: (user === null) ? require('./get-user')(service, app.info) : user, services: _.compact([service]), hijack: false, - autoRemove: true, + autoRemove, + noDeps, + prestart: !autoRemove, }, _.identity), }); diff --git a/utils/build-tooling-task.js b/utils/build-tooling-task.js index 089a07067..0bfee3ce1 100644 --- a/utils/build-tooling-task.js +++ b/utils/build-tooling-task.js @@ -1,6 +1,8 @@ 'use strict'; const _ = require('lodash'); +const remove = require('./remove'); +const path = require('path'); module.exports = (config, injected) => { // Get our defaults and such @@ -17,29 +19,61 @@ module.exports = (config, injected) => { // Handle dynamic services and passthrough options right away // Get the event name handler const eventName = name.split(' ')[0]; - const run = answers => injected.Promise.try(() => (_.isEmpty(app.compose)) ? app.init() : true) - // Kick off the pre event wrappers - .then(() => app.events.emit(`pre-${eventName}`, config, answers)) - // Get an interable of our commandz - .then(() => _.map(require('./parse-tooling-config')(cmd, service, options, answers, sapis))) - // Build run objects - .map(({command, service}) => require('./build-tooling-runner')(app, command, service, user, env, dir, appMount)) - // Try to run the task quickly first and then fallback to compose launch - .each(runner => require('./build-docker-exec')(injected, stdio, runner).catch(execError => { - return injected.engine.isRunning(runner.id).then(isRunning => { - if (!isRunning) { - return injected.engine.run(runner).catch(composeError => { - composeError.hide = true; - throw composeError; - }); - } else { - execError.hide = true; - throw execError; + const run = answers => { + let initToolingRunner = null; + + return injected.Promise.try(() => (_.isEmpty(app.compose) && '_init' !== service) ? app.init() : true) + // Kick off the pre event wrappers + .then(() => app.events.emit(`pre-${eventName}`, config, answers)) + // Get an interable of our commandz + .then(() => _.map(require('./parse-tooling-config')(cmd, service, name, options, answers, sapis))) + // Build run objects + .map( + ({command, service}) => { + if ('_init' === service) { + initToolingRunner = _.merge( + {}, + require('./build-init-runner')(_.merge( + {}, + require('./get-init-runner-defaults')(app._lando, {destination: app.root, name: app.project, _app: app}), + {cmd: command, workdir: '/app', env}, + )), + ); + + return initToolingRunner; + } + + return require('./build-tooling-runner')( + app, command, service, user, env, dir, appMount, !answers?.deps ?? false, answers?.autoRemove ?? true, + ); + }) + // Try to run the task quickly first and then fallback to compose launch + .each(runner => require('./build-docker-exec')(injected, stdio, runner).catch(execError => { + return injected.engine.isRunning(runner.id).then(isRunning => { + if (!isRunning) { + return injected.engine.run(runner).catch(composeError => { + composeError.hide = true; + throw composeError; + }); + } else { + execError.hide = true; + throw execError; + } + }); + })) + // Post event + .then(() => app.events.emit(`post-${eventName}`, config, answers)) + .finally(() => { + if (null === initToolingRunner) { + return; } + + initToolingRunner.opts = {purge: true, mode: 'attach'}; + return injected.engine.stop(initToolingRunner) + .then(() => injected.engine.destroy(initToolingRunner)) + .then(() => remove(path.dirname(initToolingRunner.compose[0]))); }); - })) - // Post event - .then(() => app.events.emit(`post-${eventName}`, config, answers)); + }; // Return our tasks return { @@ -47,5 +81,6 @@ module.exports = (config, injected) => { describe, run, options, + service, }; }; diff --git a/utils/filter-v3-build-steps.js b/utils/filter-v3-build-steps.js index fc3a2ca7e..2190f9c97 100644 --- a/utils/filter-v3-build-steps.js +++ b/utils/filter-v3-build-steps.js @@ -4,6 +4,7 @@ const _ = require('lodash'); module.exports = (services, app, rootSteps = [], buildSteps= [], prestart = false) => { const getUser = require('../utils/get-user'); + const getAppMount = require('../utils/get-app-mount'); // compute stdid based on compose major version const cstdio = _.get(app, '_config.orchestratorMV', 2) ? 'inherit' : ['inherit', 'pipe', 'pipe']; // Start collecting them @@ -29,6 +30,7 @@ module.exports = (services, app, rootSteps = [], buildSteps= [], prestart = fals mode: 'attach', cstdio, prestart, + workdir: getAppMount(service, app.info), user: (_.includes(rootSteps, section)) ? 'root' : getUser(service, app.info), services: [service], }, @@ -37,26 +39,8 @@ module.exports = (services, app, rootSteps = [], buildSteps= [], prestart = fals } }); }); - // Let's silent run user-perm stuff and add a "last" flag + // Let's add a "last" flag if (!_.isEmpty(build)) { - const permsweepers = _(build) - .map(command => ({id: command.id, services: _.get(command, 'opts.services', [])})) - .uniqBy('id') - .value(); - _.forEach(permsweepers, ({id, services}) => { - build.unshift({ - id, - cmd: '/helpers/user-perms.sh --silent', - compose: app.compose, - project: app.project, - opts: { - mode: 'attach', - prestart, - user: 'root', - services, - }, - }); - }); // Denote the last step in the build if its happening before start const last = _.last(build); last.opts.last = prestart; diff --git a/utils/get-app-mount.js b/utils/get-app-mount.js new file mode 100644 index 000000000..c8d0634c2 --- /dev/null +++ b/utils/get-app-mount.js @@ -0,0 +1,11 @@ +'use strict'; + +const _ = require('lodash'); + +module.exports = (name, info = []) => { + // if no matching service return /app + if (!_.find(info, {service: name})) return '/app'; + // otherwise get the service + const service = _.find(info, {service: name}); + return service.appMount || '/app'; +}; diff --git a/utils/get-app-mounts.js b/utils/get-app-mounts.js index 694ffedb3..521b4af70 100644 --- a/utils/get-app-mounts.js +++ b/utils/get-app-mounts.js @@ -6,7 +6,7 @@ module.exports = app => _(app.services) // Objectify .map(service => _.merge({name: service}, _.get(app, `config.services.${service}`, {}))) // Set the default - .map(config => _.merge({}, config, {app_mount: _.get(config, 'app_mount', 'cached')})) + .map(config => _.merge({}, config, {app_mount: _.get(config, 'app_mount', app.config.app_mount || 'cached')})) // Filter out disabled mountes .filter(config => config.app_mount !== false && config.app_mount !== 'disabled') // Combine together diff --git a/utils/get-app.js b/utils/get-app.js index b86883bd2..a50121cda 100644 --- a/utils/get-app.js +++ b/utils/get-app.js @@ -24,8 +24,6 @@ module.exports = (files, userConfRoot) => { if (!config.name) return {}; // cast the name to a string...just to make sure. config.name = require('../utils/slugify')(config.name); - // slugify project - config.project = require('../utils/docker-composify')(config.name); return _.merge({}, config, { configFiles: files, diff --git a/utils/get-cli-env.js b/utils/get-cli-env.js index ebaf3938f..212051bdc 100644 --- a/utils/get-cli-env.js +++ b/utils/get-cli-env.js @@ -2,6 +2,13 @@ const _ = require('lodash'); -module.exports = (more = {}) => _.merge({}, { - PHP_MEMORY_LIMIT: '-1', -}, more); +module.exports = function(more = {}) { + let githubEnvVars = {}; + if (process.env.LANDO_CLI_ENV_JSON) { + githubEnvVars = JSON.parse(process.env.LANDO_CLI_ENV_JSON); + } + + return _.merge({}, { + PHP_MEMORY_LIMIT: '-1', + }, githubEnvVars, more); +}; diff --git a/utils/get-config-defaults.js b/utils/get-config-defaults.js index ead6181e8..17c37a947 100644 --- a/utils/get-config-defaults.js +++ b/utils/get-config-defaults.js @@ -21,7 +21,7 @@ const getBuildEngineVersion = (platform = process.landoPlatform ?? process.platf // Default config const defaultConfig = options => ({ orchestratorSeparator: '_', - orchestratorVersion: '2.31.0', + orchestratorVersion: '2.40.3', configSources: [], coreBase: path.resolve(__dirname, '..'), disablePlugins: [], diff --git a/utils/get-docker-bin-path.js b/utils/get-docker-bin-path.js index ec4a00f42..3b3ecf904 100644 --- a/utils/get-docker-bin-path.js +++ b/utils/get-docker-bin-path.js @@ -5,9 +5,8 @@ const path = require('path'); module.exports = (platform = process.landoPlatform ?? process.platform) => { switch (platform) { - case 'darwin': - return '/Applications/Docker.app/Contents/Resources/bin'; case 'linux': + case 'wsl': return '/usr/share/lando/bin'; case 'win32': { const programFiles = process.env.ProgramW6432 || process.env.ProgramFiles; @@ -20,8 +19,6 @@ module.exports = (platform = process.landoPlatform ?? process.platform) => { return path.win32.join(programFiles + '\\Docker\\Docker\\resources\\bin'); } } - case 'wsl': - return '/mnt/wsl/docker-desktop/cli-tools/usr/bin'; default: return '/usr/bin'; } diff --git a/utils/get-init-runner-defaults.js b/utils/get-init-runner-defaults.js index d63666c68..f046e1c2f 100644 --- a/utils/get-init-runner-defaults.js +++ b/utils/get-init-runner-defaults.js @@ -10,6 +10,7 @@ module.exports = (lando, options) => { lando.config.userConfRoot, lando.config.home, options.destination, + _.get(options, '_app', {}), _.cloneDeep(lando.config.appEnv), _.cloneDeep(lando.config.appLabels), _.get(options, 'initImage', 'devwithlando/util:4'), @@ -21,10 +22,13 @@ module.exports = (lando, options) => { const separator = lando.config.orchestratorSeparator; // Return return { - id: [`${project}${separator}init${separator}1`], + id: `${project}${separator}init${separator}1`, project, user: 'www-data', compose: initFiles, remove: false, + workdir: '/', + prestart: true, + env: {}, }; }; diff --git a/utils/get-tasks.js b/utils/get-tasks.js index 3ae593808..9e34aed29 100644 --- a/utils/get-tasks.js +++ b/utils/get-tasks.js @@ -3,6 +3,7 @@ const _ = require('lodash'); const fs = require('fs'); const path = require('path'); +const App = require('../lib/app'); /* * Paths to / @@ -41,9 +42,10 @@ const loadCacheFile = file => { */ const appRunner = command => (argv, lando) => { const app = lando.getApp(argv._app.root); + const service = _.get(app.config, `tooling.${command}.service`, ''); return lando.events.emit('pre-app-runner', app) .then(() => lando.events.emit('pre-command-runner', app)) - .then(() => app.init().then(() => _.find(app.tasks, {command}).run(argv))); + .then(() => app.init({noEngine: '_init' === service}).then(() => _.find(app.tasks, {command}).run(argv))); }; /* @@ -53,6 +55,8 @@ const engineRunner = (config, command) => (argv, lando) => { const AsyncEvents = require('./../lib/events'); // Build a minimal app const app = lando.cache.get(path.basename(config.composeCache)); + app._lando = lando; + app._config = lando.config; app.config = config; app.events = new AsyncEvents(lando.log); @@ -129,7 +133,7 @@ module.exports = (config = {}, argv = {}, tasks = []) => { // If the tooling command is being called lets assess whether we can get away with engine bootstrap level const ids = _(config.tooling).map(task => task.id).filter(_.identity).value(); - const level = (_.includes(ids, argv._[0])) ? getBsLevel(config, argv._[0]) : 'app'; + const level = !App.isBootstrapCommand && (_.includes(ids, argv._[0])) ? getBsLevel(config, argv._[0]) : 'app'; // Load all the tasks, remember we need to remove "disabled" tasks (eg non-object tasks) here _.forEach(_.get(config, 'tooling', {}), (task, command) => { diff --git a/utils/get-tooling-defaults.js b/utils/get-tooling-defaults.js index 73d24fe0f..d4cf67649 100644 --- a/utils/get-tooling-defaults.js +++ b/utils/get-tooling-defaults.js @@ -12,7 +12,6 @@ module.exports = ({ env = {}, options = {}, service = '', - stdio = 'inherit', user = null, } = {}) => ({ @@ -25,6 +24,5 @@ module.exports = ({ describe: description, options: options, service: service, - stdio: stdio, user, }); diff --git a/utils/get-user.js b/utils/get-user.js index f5b8b2094..ed42f7b87 100644 --- a/utils/get-user.js +++ b/utils/get-user.js @@ -7,8 +7,8 @@ module.exports = (name, info = []) => { if (!_.find(info, {service: name})) return 'www-data'; // otherwise get the service const service = _.find(info, {service: name}); - // if this is a "no-api" service eg type "docker-compose" also return www-data - if (!service.api && service.type === 'docker-compose') return 'www-data'; + // if this is a "no-api" service eg type "docker-compose" return meUser or www-data as default + if (!service.api && service.type === 'docker-compose') return service.meUser || 'www-data'; // otherwise return different things based on the api return service.api === 4 ? service.user || 'www-data' : service.meUser || 'www-data'; }; diff --git a/utils/load-compose-files.js b/utils/load-compose-files.js index f13bf4cec..73a23fcee 100644 --- a/utils/load-compose-files.js +++ b/utils/load-compose-files.js @@ -2,8 +2,32 @@ const _ = require('lodash'); const Yaml = require('./../lib/yaml'); +const path = require('path'); const yaml = new Yaml(); +const fs = require('fs'); +const remove = require('./remove'); -module.exports = (files, dir) => _(require('./normalize-files')(files, dir)) - .map(file => yaml.load(file)) - .value(); +// This just runs `docker compose --project-directory ${dir} config -f ${files} --output ${outputPaths}` to +// make all paths relative to the lando config root +module.exports = async (files, dir, landoComposeConfigDir = undefined, outputConfigFunction = undefined) => { + const composeFilePaths = _(require('./normalize-files')(files, dir)).value(); + if (_.isEmpty(composeFilePaths)) { + return []; + } + + if (undefined === outputConfigFunction) { + return _(composeFilePaths) + .map(file => yaml.load(file)) + .value(); + } + + const outputFile = path.join(landoComposeConfigDir, 'resolved-compose-config.yml'); + + fs.mkdirSync(path.dirname(outputFile), {recursive: true}); + await outputConfigFunction(composeFilePaths, outputFile); + const result = yaml.load(outputFile); + fs.unlinkSync(outputFile); + remove(path.dirname(outputFile)); + + return [result]; +}; diff --git a/utils/parse-compose-services.js b/utils/parse-compose-services.js new file mode 100644 index 000000000..1a0eafe31 --- /dev/null +++ b/utils/parse-compose-services.js @@ -0,0 +1,31 @@ +'use strict'; + +// Modules +const _ = require('lodash'); + +// adds required methods to ensure the lando v3 debugger can be injected into v4 things +module.exports = (config, composeServiceIds, app) => _(config) + // Arrayify + .map((service, name) => _.merge({}, service, {name})) + // Filter out any services which are not defined in the docker compose services + .filter(service => _.includes(composeServiceIds, service.name)) + .filter(service => 4 !== service.api) + // Build the config and ensure api is set to 3 + .map(service => _.merge({}, { + _app: app, + app: app.name, + home: app.config.home || app._config.home, + project: app.project, + root: app.root, + type: '_lando-compose', + version: 'custom', + userConfRoot: app._config.userConfRoot, + api: 3, + entrypoint: null, // NOTE: Do not overwrite the entrypoint from docker compose. Or should we? + data: null, // NOTE: Do not create the data volume + dataHome: null, // NOTE: Do not create the dataHome volume + meUser: 'root', + appMount: '/', + sslExpose: false, + }, service)) + .value(); diff --git a/utils/parse-events-config.js b/utils/parse-events-config.js index 017fd3b62..8dc1fa07c 100644 --- a/utils/parse-events-config.js +++ b/utils/parse-events-config.js @@ -36,9 +36,10 @@ const getService = (cmd, data = {}, defaultService = 'appserver') => { }; // adds required methods to ensure the lando v3 debugger can be injected into v4 things -module.exports = (cmds, app, data = {}) => _.map(cmds, cmd => { +module.exports = (cmds, app, data, lando) => _.map(cmds, cmd => { // Discover the service const service = getService(cmd, data, app._defaultService); + // compute stdio based on compose major version const cstdio = _.get(app, '_config.orchestratorMV', 2) ? 'inherit' : ['inherit', 'pipe', 'pipe']; @@ -53,6 +54,25 @@ module.exports = (cmds, app, data = {}) => _.map(cmds, cmd => { // if array then just join it together if (_.isArray(cmd)) cmd = cmd.join(' '); + if ('lando' === service) { + const yargs = require('yargs'); + const argv = yargs(cmd).parse(); + const $0 = _.pullAt(argv._, [0])[0]; + const toolingTask = _.find(app.tasks, task => $0 === task.command); + argv._eventArgs = argv._; + argv.$0 = undefined; + argv._ = undefined; + argv._app = app; + + if (undefined === toolingTask) { + throw new Error('Could not find tooling command: ' + $0); + } + return { + toolingTask, + answers: argv, + }; + } + // lando 4 services // @NOTE: lando 4 service events will change once we have a complete hook system if (sapi === 4) { @@ -76,6 +96,19 @@ module.exports = (cmds, app, data = {}) => _.map(cmds, cmd => { _.get(app, 'v4.servicesList', []), ]).flatten().compact().uniq().value(); + + if ('_init' === service) { + return _.merge( + {}, + require('./build-init-runner')(_.merge( + {}, + require('./get-init-runner-defaults')(lando, {destination: app.root, name: app.project, _app: app}), + {cmd, workdir: '/app'}, + )), + {isInitEventCommand: true}, + ); + } + // Validate the service if we can // @NOTE fast engine runs might not have this data yet if ( @@ -95,6 +128,7 @@ module.exports = (cmds, app, data = {}) => _.map(cmds, cmd => { opts: { cstdio, mode: 'attach', + workdir: require('./get-app-mount')(service, app.info), user: require('./get-user')(service, app.info), services: [service], environment: { diff --git a/utils/parse-tooling-config.js b/utils/parse-tooling-config.js index e3e21cddd..1f7150355 100644 --- a/utils/parse-tooling-config.js +++ b/utils/parse-tooling-config.js @@ -20,11 +20,11 @@ const getDynamicKeys = (answer, answers = {}) => _(answers) * Set SERVICE from answers and strip out that noise from the rest of * stuff, check answers/argv for --service or -s, validate and then remove */ -const handleDynamic = (config, options, answers = {}, sapis = {}) => { +const handleDynamic = (config, argv, answers = {}, sapis = {}) => { if (_.startsWith(config.service, ':')) { const answer = answers[config.service.split(':')[1]]; // Remove dynamic service option from argv - _.remove(process.argv, arg => _.includes(getDynamicKeys(answer, answers).concat(answer), arg)); + _.remove(argv, arg => _.includes(getDynamicKeys(answer, answers).concat(answer), arg)); // get the service const service = answers[config.service.split(':')[1]]; // Return updated config @@ -41,9 +41,9 @@ const handleDynamic = (config, options, answers = {}, sapis = {}) => { * the first three assuming they are [node, lando.js, options.name]' * Check to see if we have global lando opts and remove them if we do */ -const handleOpts = (config, argopts = []) => { +const handleOpts = (config, name, argv, argopts = []) => { // Append any user specificed opts - argopts = argopts.concat(process.argv.slice(3)); + argopts = argopts.concat(argv.slice(argv.findIndex(value => value === name.split(' ')[0]) + 1)); // If we have no args then just return right away if (_.isEmpty(argopts)) return config; // Return @@ -74,13 +74,13 @@ const parseCommand = (cmd, service, sapis) => { }; // adds required methods to ensure the lando v3 debugger can be injected into v4 things -module.exports = (cmd, service, options = {}, answers = {}, sapis = {}) => _(cmd) +module.exports = (cmd, service, name, options = {}, answers = {}, sapis = {}) => _(cmd) // Put into an object so we can handle "multi-service" tooling .map(cmd => parseCommand(cmd, service, sapis)) // Handle dynamic services - .map(config => handleDynamic(config, options, answers, sapis)) + .map(config => handleDynamic(config, answers._eventArgs ?? process.argv, answers, sapis)) // Add in any argv extras if they've been passed in - .map(config => handleOpts(config, handlePassthruOpts(options, answers))) + .map(config => handleOpts(config, name, answers._eventArgs ?? process.argv, handlePassthruOpts(options, answers))) // Wrap the command in /bin/sh if that makes sense .map(config => ({...config, command: require('./shell-escape')(config.command, true, config.args, config.sapi)})) // Add any args to the command and compact to remove undefined diff --git a/utils/parse-v3-services.js b/utils/parse-v3-services.js index cfed7f138..5707a322e 100644 --- a/utils/parse-v3-services.js +++ b/utils/parse-v3-services.js @@ -20,7 +20,7 @@ module.exports = (config, app) => _(config) app: app.name, confDest: path.join(app._config.userConfRoot, 'config', service.type.split(':')[0]), data: `data_${service.name}`, - home: app._config.home, + home: app.config.home || app._config.home, project: app.project, root: app.root, type: service.type.split(':')[0], diff --git a/utils/parse-v4-services.js b/utils/parse-v4-services.js index 3d5dc2fd1..567be08b1 100644 --- a/utils/parse-v4-services.js +++ b/utils/parse-v4-services.js @@ -5,6 +5,7 @@ const _ = require('lodash'); // adds required methods to ensure the lando v3 debugger can be injected into v4 things module.exports = services => _(services) + .pickBy(service => null !== service) .map((service, name) => { const type = service.type ?? 'lando'; return _.merge({}, { diff --git a/utils/run-init.js b/utils/run-init.js index 9d432d16b..372775d21 100644 --- a/utils/run-init.js +++ b/utils/run-init.js @@ -1,5 +1,8 @@ 'use strict'; +const remove = require('../utils/remove'); +const path = require('path'); + // Helper to kill a run const killRun = config => ({ id: config.id, @@ -13,7 +16,9 @@ const killRun = config => ({ // adds required methods to ensure the lando v3 debugger can be injected into v4 things module.exports = (lando, run) => lando.engine.run(run).catch(err => { + return lando.Promise.reject(err); +}).finally(() => { return lando.engine.stop(killRun(run)) .then(() => lando.engine.destroy(killRun(run))) - .then(() => lando.Promise.reject(err)); + .then(() => remove(path.dirname(run.compose[0]))); });