Skip to content
This repository was archived by the owner on Jan 26, 2024. It is now read-only.
Permalink

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: angular-schule/ngx-deploy-starter
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: master
Choose a base ref
...
head repository: bikecoders/ngx-deploy-npm
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: main
Choose a head ref
Can’t automatically merge. Don’t worry, you can still create the pull request.

Commits on Sep 2, 2019

  1. Copy the full SHA
    39aac2b View commit details
  2. Copy the full SHA
    da28b27 View commit details
  3. Copy the full SHA
    6283731 View commit details

Commits on Sep 3, 2019

  1. Copy the full SHA
    66adc24 View commit details
  2. Copy the full SHA
    fb2a9e3 View commit details

Commits on Sep 4, 2019

  1. Copy the full SHA
    97344c4 View commit details
  2. Merge pull request #1 from bikecoders/ngx-npm-deploy

    Create npm deployer
    dianjuar committed Sep 4, 2019
    Copy the full SHA
    141614d View commit details
  3. 1.0.0

    dianjuar committed Sep 4, 2019
    Copy the full SHA
    0fe7474 View commit details

Commits on Sep 8, 2019

  1. Copy the full SHA
    95471f2 View commit details
  2. Merge pull request #3 from bikecoders/fix-deploy-production

    fix: do not force to build on production
    dianjuar authored Sep 8, 2019

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    d2172c1 View commit details
  3. 1.0.1

    dianjuar committed Sep 8, 2019
    Copy the full SHA
    83dbd77 View commit details

Commits on Sep 9, 2019

  1. Copy the full SHA
    31f9003 View commit details
  2. Merge pull request #4 from bikecoders/remove-complex-logic

    refactor: remove complex logic
    dianjuar authored Sep 9, 2019

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    2d8c29d View commit details
  3. 1.0.2

    dianjuar committed Sep 9, 2019
    Copy the full SHA
    42cbbf0 View commit details

Commits on Sep 11, 2019

  1. docs: create ci instructions

    dianjuar committed Sep 11, 2019
    Copy the full SHA
    0b1f5f5 View commit details
  2. Merge pull request #5 from bikecoders/create-ci-instructions

    docs: create ci instructions
    dianjuar authored Sep 11, 2019

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    f0e52f3 View commit details
  3. 1.0.3

    dianjuar committed Sep 11, 2019
    Copy the full SHA
    a32199b View commit details

Commits on Sep 12, 2019

  1. feat: add standard-version

    osnoser1 committed Sep 12, 2019
    Copy the full SHA
    bc76130 View commit details

Commits on Sep 18, 2019

  1. Merge pull request #7 from osnoser1/feature/changelog-generation

    feat: add standard-version
    dianjuar authored Sep 18, 2019

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    2081821 View commit details
  2. Copy the full SHA
    ced3480 View commit details
  3. Merge pull request #8 from bikecoders/set-changelog

    docs: tweak change log generation
    dianjuar authored Sep 18, 2019

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    2af1170 View commit details
  4. chore(release): 1.0.4

    dianjuar committed Sep 18, 2019
    Copy the full SHA
    6e98146 View commit details
  5. docs: fix cover typo

    dianjuar committed Sep 18, 2019
    Copy the full SHA
    3eecf47 View commit details
  6. Merge pull request #9 from bikecoders/fix-cover-typo

    Fix cover typo
    dianjuar authored Sep 18, 2019

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    0c815f0 View commit details
  7. chore(release): 1.0.5

    dianjuar committed Sep 18, 2019
    Copy the full SHA
    4e22b08 View commit details

Commits on Oct 19, 2019

  1. Copy the full SHA
    bc92d52 View commit details
  2. Merge pull request #11 from bikecoders/improve-testing

    test: refactor the test of the ng-add
    dianjuar authored Oct 19, 2019

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    95e5c7a View commit details

Commits on Oct 21, 2019

  1. feat: add nx compatibility

    dianjuar committed Oct 21, 2019
    Copy the full SHA
    9618c3b View commit details
  2. Merge pull request #12 from bikecoders/add-nx-compatibility

    feat: set deployer only on publishable libraries
    dianjuar authored Oct 21, 2019

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    902782b View commit details
  3. Copy the full SHA
    58dcbc9 View commit details
  4. Merge pull request #13 from bikecoders/better-changelog-mangement

    docs: set changelog on project root
    dianjuar authored Oct 21, 2019

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    4bfbd80 View commit details
  5. docs: update readme

    Update readme with information about README, LICENCE and CHANGELOG files
    dianjuar committed Oct 21, 2019
    Copy the full SHA
    414ae4a View commit details
  6. Merge pull request #14 from bikecoders/update-next-milestones

    docs: update readme
    dianjuar authored Oct 21, 2019

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    0f424ed View commit details
  7. chore(release): 1.1.0

    dianjuar committed Oct 21, 2019
    Copy the full SHA
    c3c3f72 View commit details

Commits on Nov 6, 2019

  1. fix: save package in devDependencies

    In the latest versions of the CLI `ng-add` packages can be added to `devDependencies` and this package is perfect for such use case since it's only needed for development.
    
    See: angular/angular-cli#15815
    alan-agius4 authored Nov 6, 2019

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    97d1ee1 View commit details

Commits on Nov 7, 2019

  1. Merge pull request #15 from alan-agius4/patch-1

    fix: save package in devDependencies
    dianjuar authored Nov 7, 2019

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    877c281 View commit details
  2. chore(release): 1.1.1

    Diego A. Juliao Armenta authored and Diego A. Juliao Armenta committed Nov 7, 2019
    Copy the full SHA
    f93ab78 View commit details

Commits on Jan 22, 2020

  1. Copy the full SHA
    3e48d36 View commit details

Commits on Jan 24, 2020

  1. feat: add a packageVersion to options

    Emery authored and dianjuar committed Jan 24, 2020
    Copy the full SHA
    eb23865 View commit details

Commits on Jan 25, 2020

  1. Copy the full SHA
    c52afb2 View commit details
  2. Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    6fe66bc View commit details
  3. Merge pull request #18 from bikecoders/feature-add-package-version-op…

    …tions
    
    feat: add package version options
    dianjuar authored Jan 25, 2020

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    66f142a View commit details
  4. Merge pull request #17 from bikecoders/dependabot/npm_and_yarn/src/ha…

    …ndlebars-4.7.2
    
    chore(deps): bump handlebars from 4.1.2 to 4.7.2 in /src
    dianjuar authored Jan 25, 2020

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    c842f5d View commit details
  5. chore(release): 1.2.0

    dianjuar committed Jan 25, 2020
    Copy the full SHA
    618a4c7 View commit details

Commits on Apr 6, 2020

  1. chore(deps): bump acorn from 5.7.3 to 5.7.4 in /src

    Bumps [acorn](https://github.com/acornjs/acorn) from 5.7.3 to 5.7.4.
    - [Release notes](https://github.com/acornjs/acorn/releases)
    - [Commits](acornjs/acorn@5.7.3...5.7.4)
    
    Signed-off-by: dependabot[bot] <support@github.com>
    dependabot[bot] authored Apr 6, 2020

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    835a571 View commit details

Commits on Apr 28, 2020

  1. chore: add github template

    dianjuar committed Apr 28, 2020
    Copy the full SHA
    a4c8cf3 View commit details
  2. Merge pull request #20 from bikecoders/create-pr-template

    chore: add github template
    dianjuar authored Apr 28, 2020

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    6df0e78 View commit details
  3. chore: add github template

    dianjuar committed Apr 28, 2020
    Copy the full SHA
    db897a3 View commit details
  4. Merge pull request #21 from bikecoders/create-pr-template

    chore: add github template
    dianjuar authored Apr 28, 2020

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    b8c1e8c View commit details
  5. Copy the full SHA
    aec3d05 View commit details
Showing with 20,314 additions and 6,692 deletions.
  1. +0 −27 .circleci/config.yml
  2. +1 −1 .editorconfig
  3. +4 −0 .github/.prettierignore
  4. +13 −0 .github/FUNDING.yml
  5. +11 −0 .github/actions/download-build/action.yml
  6. +11 −0 .github/actions/download-coverage-report/action.yml
  7. +11 −0 .github/actions/download-lint-report/action.yml
  8. +16 −0 .github/actions/migrate/action.yml
  9. +51 −0 .github/actions/setup/action.yml
  10. +23 −0 .github/pull_request_template.md
  11. +45 −0 .github/workflows/backwards-compatibility-test.yml
  12. +65 −0 .github/workflows/basic-test.yml
  13. +29 −0 .github/workflows/compatibility-observability.yml
  14. +17 −0 .github/workflows/e2e-test.yml
  15. +69 −0 .github/workflows/pr.yml
  16. +80 −0 .github/workflows/publishment.yml
  17. +61 −0 .github/workflows/smoke-test-nx-workspace.yml
  18. +51 −0 .github/workflows/sonar-pr.yml
  19. +50 −0 .github/workflows/test-nx-next.yml
  20. +62 −26 .gitignore
  21. +4 −0 .husky/commit-msg
  22. +4 −0 .husky/pre-commit
  23. +1 −0 .nvmrc
  24. +5 −0 .prettierignore
  25. +4 −0 .prettierrc
  26. +28 −0 .verdaccio/config.yml
  27. +8 −0 .vscode/extensions.json
  28. +3 −0 .vscode/settings.json
  29. +11 −11 .vscode/tasks.json
  30. +128 −0 CODE_OF_CONDUCT.md
  31. +1 −1 LICENSE
  32. +280 −44 README.md
  33. +26 −0 docker-compose.yml
  34. +102 −79 docs/README_contributors.md
  35. BIN docs/cover.png
  36. BIN docs/cover.xcf
  37. BIN docs/ng-deploy-starter-project.jpg
  38. +86 −0 docs/ngx-deploy-npm-logo.svg
  39. +40 −0 eslint.config.js
  40. +5 −0 jest.config.ts
  41. +3 −0 jest.preset.js
  42. +48 −0 nx.json
  43. +14,925 −0 package-lock.json
  44. +70 −0 package.json
  45. +3 −0 packages/ngx-deploy-npm-e2e/.env.example
  46. +20 −0 packages/ngx-deploy-npm-e2e/eslint.config.js
  47. +11 −0 packages/ngx-deploy-npm-e2e/jest.config.ts
  48. +31 −0 packages/ngx-deploy-npm-e2e/project.json
  49. +80 −0 packages/ngx-deploy-npm-e2e/src/install.spec.ts
  50. +11 −0 packages/ngx-deploy-npm-e2e/src/package-installation.spec.ts
  51. +22 −0 packages/ngx-deploy-npm-e2e/src/publish-minimal-lib.smoke.spec.ts
  52. +55 −0 packages/ngx-deploy-npm-e2e/src/publish.spec.ts
  53. +224 −0 packages/ngx-deploy-npm-e2e/src/utils/basic-setup.ts
  54. +24 −0 packages/ngx-deploy-npm-e2e/src/utils/generate-lib.ts
  55. +13 −0 packages/ngx-deploy-npm-e2e/src/utils/get-nx-workspace-version.ts
  56. +5 −0 packages/ngx-deploy-npm-e2e/src/utils/index.ts
  57. +11 −0 packages/ngx-deploy-npm-e2e/src/utils/install-deps.ts
  58. +27 −0 packages/ngx-deploy-npm-e2e/src/utils/utils-ngx-deploy-npm.ts
  59. +10 −0 packages/ngx-deploy-npm-e2e/tsconfig.json
  60. +15 −0 packages/ngx-deploy-npm-e2e/tsconfig.spec.json
  61. +10 −0 packages/ngx-deploy-npm/.babelrc
  62. +482 −0 packages/ngx-deploy-npm/CHANGELOG.md
  63. +25 −0 packages/ngx-deploy-npm/eslint.config.js
  64. +10 −0 packages/ngx-deploy-npm/executors.json
  65. +12 −0 packages/ngx-deploy-npm/generators.json
  66. +16 −0 packages/ngx-deploy-npm/jest.config.ts
  67. +14 −0 packages/ngx-deploy-npm/migrations.json
  68. +44 −0 packages/ngx-deploy-npm/package.json
  69. +98 −0 packages/ngx-deploy-npm/project.json
  70. +16 −0 packages/ngx-deploy-npm/src/__mocks__/@nx/devkit/index.ts
  71. +41 −0 packages/ngx-deploy-npm/src/__mocks__/child_process/index.ts
  72. +116 −0 packages/ngx-deploy-npm/src/__mocks__/mocks.ts
  73. +1 −0 packages/ngx-deploy-npm/src/core/index.ts
  74. +4 −0 packages/ngx-deploy-npm/src/core/npm-access.enum.ts
  75. +45 −0 packages/ngx-deploy-npm/src/executors/deploy/actions.spec.ts
  76. +14 −0 packages/ngx-deploy-npm/src/executors/deploy/actions.ts
  77. +277 −0 packages/ngx-deploy-npm/src/executors/deploy/engine/engine.spec.ts
  78. +149 −0 packages/ngx-deploy-npm/src/executors/deploy/engine/engine.ts
  79. +22 −0 packages/ngx-deploy-npm/src/executors/deploy/executor.ts
  80. +34 −0 packages/ngx-deploy-npm/src/executors/deploy/schema.d.ts
  81. +47 −0 packages/ngx-deploy-npm/src/executors/deploy/schema.json
  82. +3 −0 packages/ngx-deploy-npm/src/executors/deploy/utils/index.ts
  83. +6 −0 packages/ngx-deploy-npm/src/executors/deploy/utils/interfaces.ts
  84. +54 −0 packages/ngx-deploy-npm/src/executors/deploy/utils/set-package-version.spec.ts
  85. +19 −0 packages/ngx-deploy-npm/src/executors/deploy/utils/set-package-version.ts
  86. +138 −0 packages/ngx-deploy-npm/src/executors/deploy/utils/spawn-async.spec.ts
  87. +35 −0 packages/ngx-deploy-npm/src/executors/deploy/utils/spawn-async.ts
  88. +283 −0 packages/ngx-deploy-npm/src/generators/install/generator.spec.ts
  89. +51 −0 packages/ngx-deploy-npm/src/generators/install/generator.ts
  90. +14 −0 packages/ngx-deploy-npm/src/generators/install/schema.d.ts
  91. +25 −0 packages/ngx-deploy-npm/src/generators/install/schema.json
  92. +3 −0 packages/ngx-deploy-npm/src/index.ts
  93. +463 −0 packages/ngx-deploy-npm/src/migrations/8.0.0/replace-buildtarget-for-depends-on.spec.ts
  94. +149 −0 packages/ngx-deploy-npm/src/migrations/8.0.0/replace-buildtarget-for-depends-on.ts
  95. +132 −0 packages/ngx-deploy-npm/src/migrations/8.0.0/write-dist-folder-path-on-deploy-target-options.spec.ts
  96. +49 −0 packages/ngx-deploy-npm/src/migrations/8.0.0/write-dist-folder-path-on-deploy-target-options.ts
  97. +45 −0 packages/ngx-deploy-npm/src/utils/file-utils.spec.ts
  98. +15 −0 packages/ngx-deploy-npm/src/utils/file-utils.ts
  99. +2 −0 packages/ngx-deploy-npm/src/utils/index.ts
  100. +85 −0 packages/ngx-deploy-npm/src/utils/is-a-lib.spec.ts
  101. +15 −0 packages/ngx-deploy-npm/src/utils/is-a-lib.ts
  102. +13 −0 packages/ngx-deploy-npm/tsconfig.json
  103. +11 −0 packages/ngx-deploy-npm/tsconfig.lib.json
  104. +16 −0 packages/ngx-deploy-npm/tsconfig.spec.json
  105. +14 −0 project.json
  106. +25 −0 renovate.json
  107. +32 −0 sonar-project.properties
  108. +0 −64 src/README.md
  109. +0 −10 src/builders.json
  110. +0 −9 src/collection.json
  111. +0 −86 src/deploy/actions.spec.ts
  112. +0 −37 src/deploy/actions.ts
  113. +0 −65 src/deploy/builder.ts
  114. +0 −19 src/deploy/schema.json
  115. +0 −10 src/engine/engine.spec.ts
  116. +0 −25 src/engine/engine.ts
  117. +0 −1 src/index.ts
  118. +0 −254 src/ng-add.spec.ts
  119. +0 −88 src/ng-add.ts
  120. +0 −5,727 src/package-lock.json
  121. +0 −72 src/package.json
  122. +0 −3 src/public_api.ts
  123. +0 −33 src/tsconfig.json
  124. +38 −0 tools/docker/sonarqube/change_password.sh
  125. +2 −0 tools/docker/sonarqube/env_vars
  126. +5 −0 tools/scripts/scripts.d.ts
  127. +23 −0 tools/scripts/start-local-registry.ts
  128. +10 −0 tools/scripts/stop-local-registry.ts
  129. +24 −0 tools/sonarqube-linter-reporter.js
  130. +24 −0 tsconfig.base.json
27 changes: 0 additions & 27 deletions .circleci/config.yml

This file was deleted.

2 changes: 1 addition & 1 deletion .editorconfig
Original file line number Diff line number Diff line change
@@ -10,4 +10,4 @@ trim_trailing_whitespace = true

[*.md]
max_line_length = off
trim_trailing_whitespace = false
trim_trailing_whitespace = true
4 changes: 4 additions & 0 deletions .github/.prettierignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# Add files here to ignore them from prettier formatting

/dist
/coverage
13 changes: 13 additions & 0 deletions .github/FUNDING.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# These are supported funding model platforms

github: [dianjuar] # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
# patreon: # Replace with a single Patreon username
# open_collective: # Replace with a single Open Collective username
# ko_fi: # Replace with a single Ko-fi username
# tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
# community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
# liberapay: # Replace with a single Liberapay username
# issuehunt: # Replace with a single IssueHunt username
# otechie: # Replace with a single Otechie username
# lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry
# custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
11 changes: 11 additions & 0 deletions .github/actions/download-build/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
name: Download Builds
description: Downloading the github artifact created with the build (dist folder)

runs:
using: composite
steps:
- name: Download dist folder
uses: actions/download-artifact@v4
with:
name: library-dist
path: dist
11 changes: 11 additions & 0 deletions .github/actions/download-coverage-report/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
name: Download Coverage Reports
description: Download the github artifact created with the `test --coverage`

runs:
using: composite
steps:
- name: Download coverage report
uses: actions/download-artifact@v4
with:
name: ngx-deploy-npm-coverage-report
path: coverage/packages/ngx-deploy-npm
11 changes: 11 additions & 0 deletions .github/actions/download-lint-report/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
name: Download Lint Reports
description: Download the github artifact with the lint report

runs:
using: composite
steps:
- name: Download lint report
uses: actions/download-artifact@v4
with:
name: lint-report.info
path: reports
16 changes: 16 additions & 0 deletions .github/actions/migrate/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
name: Migrate Nx Workspace
description: Migrate Nx Workspace to the specified version
inputs:
nx-version:
description: Nx version to try to migrate
required: true

runs:
using: composite
steps:
- name: Migrate
shell: bash
run: |
npx nx migrate ${{ inputs.nx-version }}
npm install --force --ignore-scripts
npx nx migrate --run-migrations --if-exists
51 changes: 51 additions & 0 deletions .github/actions/setup/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
name: Setup
description: Setup Node.js, cache and install dependencies
inputs:
node-version:
description: 'The Node.js version to use. The default one is going to be the defined on the .nvmrc file.'
required: false
git_bot_token:
description: Git Bot token used to push to protected branches because github token can't
required: false

runs:
using: composite
steps:
- name: Checkout all commits
uses: actions/checkout@v4
with:
token: ${{ inputs.git_bot_token || github.token }}
fetch-depth: 0

- name: git config
shell: bash
run: |
git config user.name "ngx-deploy-npm Bot"
git config user.email "-"
- name: Set Node.js version based on .nvmrc or Action Input
shell: bash
run: |
if [ -z "${{ inputs.node-version }}" ]; then
version=$(cat .nvmrc)
echo "Setting default value for node-version parameter: $version"
echo "NODE_VERSION=$version" >> $GITHUB_ENV
else
echo "Using provided value for node-version parameter: ${{ inputs.node-version }}"
echo "NODE_VERSION=${{ inputs.node-version }}" >> $GITHUB_ENV
fi
- name: Use Node.js
uses: actions/setup-node@v4
with:
node-version: ${{ env.NODE_VERSION }}
cache: 'npm'
# This doesn't just set the registry url, but also sets
# the right configuration in .npmrc that reads NPM token
# from NPM_AUTH_TOKEN environment variable.
# It actually creates a .npmrc in a temporary folder
# and sets the NPM_CONFIG_USERCONFIG environment variable.
registry-url: https://registry.npmjs.org
- name: npm install
shell: bash
run: npm ci
23 changes: 23 additions & 0 deletions .github/pull_request_template.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
## PR Checklist

Please check if your PR fulfills the following requirements:

- [ ] Tests for the changes have been added (for bug fixes/features)
- [ ] Docs have been added/updated (for bug fixes/features)

## What is the current behavior?

<!-- Please describe the current behavior that you are modifying or link to a relevant issue. -->

Issue Number: N/A

## What is the new behavior?

## Does this PR introduce a breaking change?

- [ ] Yes
- [ ] No

<!-- If this PR contains a breaking change, please describe the impact and migration path for existing applications below. -->

## Other information
45 changes: 45 additions & 0 deletions .github/workflows/backwards-compatibility-test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
name: Backwards Compatibility Test

on: workflow_call

jobs:
backwards-compatibility-test:
strategy:
matrix:
include:
# '' means current workspace version
- nx-version: ''
node-version: 22
- nx-version: ''
node-version: 20

- nx-version: 'previous'
node-version: 22
- nx-version: 'previous'
node-version: 20
- nx-version: 'previous'
node-version: 18

- nx-version: '18.3.5'
node-version: 20
- nx-version: '18.3.5'
node-version: 18

- nx-version: '17.3.2'
node-version: 20
- nx-version: '17.3.2'
node-version: 18

- nx-version: '16.10.0'
node-version: 20
- nx-version: '16.10.0'
node-version: 18
- nx-version: '16.10.0'
node-version: 16 # This node's version is deprecated

name: Backwards Compatibility Test
uses: ./.github/workflows/smoke-test-nx-workspace.yml
with:
nx-version: ${{matrix.nx-version}}
node-version: ${{matrix.node-version}}
verbose: true
65 changes: 65 additions & 0 deletions .github/workflows/basic-test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
name: Essential Test

on: workflow_call

jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: ./.github/actions/setup

- name: Lint
run: npx nx lint ngx-deploy-npm

- name: Lint report
run: npx nx lint-report ngx-deploy-npm

- name: Archive lint report results
uses: actions/upload-artifact@v4
with:
name: lint-report.info
path: reports

build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: ./.github/actions/setup

- run: npx nx build ngx-deploy-npm

- name: Archive build result
uses: actions/upload-artifact@v4
with:
name: library-dist
path: dist

unit-test:
strategy:
matrix:
os: [ubuntu-latest, windows-latest]
node-version: [20, 22]
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
- uses: ./.github/actions/setup
with:
node-version: ${{ matrix.node-version }}

- run: npx nx test ngx-deploy-npm --configuration="ci"

- name: Set Node.js version based on .nvmrc
shell: bash
run: |
version=$(cat .nvmrc)
echo "Setting default value for node-version parameter: $version"
echo "NODE_VERSION=$version" >> $GITHUB_ENV
echo "$NODE_VERSION"
- if: ${{ matrix.os == 'ubuntu-latest' && matrix.node-version == env.NODE_VERSION }}
name: Archive coverage report
uses: actions/upload-artifact@v4
with:
name: ngx-deploy-npm-coverage-report
path: coverage/packages/ngx-deploy-npm/lcov.info
29 changes: 29 additions & 0 deletions .github/workflows/compatibility-observability.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
name: Incoming Versions Compatibility Test
on:
workflow_dispatch:
schedule:
- cron: '0 0 * * *' # daily at 00:00

jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: ./.github/actions/setup

- run: npx nx build ngx-deploy-npm

- name: Archive build result
uses: actions/upload-artifact@v4
with:
name: library-dist
path: dist

backwards-compatibility-test:
needs: build
strategy:
matrix:
nx-version: [latest]
uses: ./.github/workflows/smoke-test-nx-workspace.yml
with:
nx-version: ${{matrix.nx-version}}
17 changes: 17 additions & 0 deletions .github/workflows/e2e-test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
name: E2E Test

on: workflow_call

jobs:
e2e-test:
strategy:
matrix:
os: [ubuntu-latest, windows-latest]
node-version: [20, 22]
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
- uses: ./.github/actions/setup
with:
node-version: ${{ matrix.node-version }}
- run: npx nx e2e ngx-deploy-npm-e2e --configuration="ci"
69 changes: 69 additions & 0 deletions .github/workflows/pr.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
name: PRs
on:
pull_request:
types: [opened, synchronize, reopened]

env:
PR_NUMBER: ${{ github.event.pull_request.number }}

jobs:
pr-test:
uses: ./.github/workflows/basic-test.yml

pr-e2e-test:
uses: ./.github/workflows/e2e-test.yml

check-commit-lint:
name: Check commit message follows guidelines
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: ./.github/actions/setup

- name: Execute commitlint
run: npx commitlint --from="origin/$BASE_REF"
env:
BASE_REF: ${{ github.base_ref }}

check-file-format:
name: Check files changes follow guidelines
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: ./.github/actions/setup

- name: Check if Prettier was run
run: npx pretty-quick --check

backwards-compatibility-test:
name: Backwards compatibility test
needs: [pr-test]
uses: ./.github/workflows/backwards-compatibility-test.yml

# Test sonarcloud analysis
# pr-analysis:
# name: SonarCloud Pr Analysis
# runs-on: ubuntu-latest
# needs: pr-test
# steps:
# - uses: actions/checkout@v4
# with:
# fetch-depth: 0 # Shallow clones should be disabled for a better relevancy of analysis
# # Download reports
# - uses: ./.github/actions/download-coverage-report
# - uses: ./.github/actions/download-lint-report
# - name: 'Verify reports'
# run: |
# pwd
# echo 'Lint report'
# ls -la ./coverage/packages/ngx-deploy-npm/lcov.info
# echo 'Coverage report'
# ls -la ./reports/ngx-deploy-npm/lint-report.info
# - name: SonarCloud Scan
# uses: SonarSource/sonarqube-scan-action@v4.2.1
# env:
# GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # Needed to get PR information, if any
# SONAR_TOKEN: ${{ secrets.SONARQUBE_SCANNER }}
# with:
# args: >
# -Dsonar.pullrequest.key=${{ github.env.PR_NUMBER }}
80 changes: 80 additions & 0 deletions .github/workflows/publishment.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
name: Publishment
on:
push:
branches:
- main

jobs:
test:
uses: ./.github/workflows/basic-test.yml

e2e-test:
uses: ./.github/workflows/e2e-test.yml

backwards-compatibility-test:
needs: [test]
uses: ./.github/workflows/backwards-compatibility-test.yml

analysis:
name: SonarCloud Main Analysis
runs-on: ubuntu-latest
needs: test
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0 # Shallow clones should be disabled for a better relevancy of analysis

# Download reports
- uses: ./.github/actions/download-coverage-report
- uses: ./.github/actions/download-lint-report

- name: SonarCloud Scan
uses: SonarSource/sonarcloud-github-action@latest
env:
SONAR_TOKEN: ${{ secrets.SONARQUBE_SCANNER }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

release-preliminar:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: ./.github/actions/setup

- name: Preliminar Version
run: npx nx version ngx-deploy-npm --dry-run

release:
environment: production
runs-on: ubuntu-latest
needs: [release-preliminar, e2e-test, test, backwards-compatibility-test]
steps:
- uses: actions/checkout@v4
- uses: ./.github/actions/setup
with:
git_bot_token: ${{ secrets.GIT_BOT_TOKEN }}

- uses: ./.github/actions/download-build

- name: Check npm credentials
run: npm whoami
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
# Use npx instead of yarn because yarn automagically sets NPM_* environment variables
# like NPM_CONFIG_REGISTRY so npm publish ends up ignoring the .npmrc file
# which is set up by `setup-node` action.

- name: Version and Publishment
run: npx nx version ngx-deploy-npm
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}

- name: Tag last-release
run: git tag --force last-release

- name: Push changes
uses: ad-m/github-push-action@master
with:
github_token: ${{ secrets.GIT_BOT_TOKEN }}
branch: ${{ github.ref }}
force: true
tags: true
61 changes: 61 additions & 0 deletions .github/workflows/smoke-test-nx-workspace.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
# This can be seen as a automated simple manual test
# It test the plugin with a real Nx workspace using a specific workspace version
name: Smoketest Nx Workspace

on:
workflow_call:
inputs:
nx-version:
type: string
description: The Nx Workspace version to run the smoke test
required: false
node-version:
type: string
description: The Node version to run the smoke test
required: false
verbose:
type: boolean
description: Enable verbose mode
required: false
default: false

jobs:
smoke-test:
runs-on: ubuntu-latest
steps:
- name: Indicate versions
run: |
if [ -n "${{ inputs.node-version }}" ]; then
echo "Using Node version: ${{ inputs.node-version }}"
else
echo "Using default Node version from .npmrc file"
fi
if [ -n "${{ inputs.nx-version }}" ]; then
echo "Using Nx version: ${{ inputs.nx-version }}"
else
echo "Using default Nx version from package.json file"
fi
- uses: actions/checkout@v4
- uses: ./.github/actions/setup
with:
node-version: ${{ inputs.node-version }}

- uses: ./.github/actions/download-build

- name: Read package.json Nx and set default parameter
id: set-default-param
run: |
if [ -z "${{ inputs.nx-version }}" ]; then
version=$(jq -r '.devDependencies.nx' package.json)
echo "Setting default value for nx-version parameter: $version"
echo "NGX_DEPLOY_NPM_E2E__NX_VERSION=$version" >> $GITHUB_ENV
else
echo "Using provided value for nx-version parameter: ${{ inputs.nx-version }}"
echo "NGX_DEPLOY_NPM_E2E__NX_VERSION=${{ inputs.nx-version }}" >> $GITHUB_ENV
fi
- name: Smoke Test
run: npx nx smoke ngx-deploy-npm-e2e --verbose=${{ inputs.verbose }}
51 changes: 51 additions & 0 deletions .github/workflows/sonar-pr.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
name: Sonar for PRs
on:
workflow_run:
workflows: ['PRs']
types: [completed]
jobs:
sonar:
name: Sonar
runs-on: ubuntu-latest
timeout-minutes: 30
if: github.event.workflow_run.conclusion == 'success'
steps:
- uses: actions/checkout@v4
with:
repository: ${{ github.event.workflow_run.head_repository.full_name }}
ref: ${{ github.event.workflow_run.head_branch }}
fetch-depth: 0

- name: Debug Workflow Info
run: |
echo "Workflow Run ID: ${{ github.event.workflow_run.id }}"
echo "Workflow Head SHA: ${{ github.event.workflow_run.head_sha }}"
echo "Current SHA: ${{ github.sha }}"
echo "Head Branch: ${{ github.event.workflow_run.head_branch }}"
echo "Repository: ${{ github.event.workflow_run.head_repository.full_name }}"
# Output raw context for additional details
echo "=== Raw Context ==="
echo "Workflow Head SHA: ${{ github.event.workflow_run }}"
- name: 'Raw Context'
uses: actions/github-script@v6
with:
script: |
JSON.stringify(${{ github.event.workflow_run }}, null, 2)
# Download reports
- uses: ./.github/actions/download-coverage-report
- uses: ./.github/actions/download-lint-report

- name: SonarCloud Scan
uses: SonarSource/sonarqube-scan-action@v4.2.1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
SONAR_TOKEN: ${{ secrets.SONARQUBE_SCANNER }}
with:
args: >
-Dsonar.scm.revision=${{ github.event.workflow_run.head_sha }}
-Dsonar.pullrequest.key=${{ github.event.workflow_run.pull_requests[0].number }}
-Dsonar.pullrequest.branch=${{ github.event.workflow_run.pull_requests[0].head.ref }}
-Dsonar.pullrequest.base=${{ github.event.workflow_run.pull_requests[0].base.ref }}
50 changes: 50 additions & 0 deletions .github/workflows/test-nx-next.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
name: Test nx@next
on:
workflow_dispatch:
schedule:
- cron: '0 0 * * *' # daily at 00:00

jobs:
lint-next:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: ./.github/actions/setup
- uses: ./.github/actions/migrate
with:
nx-version: 'next'
- name: Lint
run: npx nx lint ngx-deploy-npm --fix

build-next:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: ./.github/actions/setup
- uses: ./.github/actions/migrate
with:
nx-version: 'next'
- name: Build
run: npx nx build ngx-deploy-npm

test-next:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: ./.github/actions/setup
- uses: ./.github/actions/migrate
with:
nx-version: 'next'
- name: Test
run: npx nx test ngx-deploy-npm

e2e-next:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: ./.github/actions/setup
- uses: ./.github/actions/migrate
with:
nx-version: 'next'
- name: E2E Test
run: npx nx e2e ngx-deploy-npm-e2e
88 changes: 62 additions & 26 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,27 +1,63 @@
node_modules
.tmp
.sass-cache
# See http://help.github.com/ignore-files/ for more about ignoring files.

# compiled output
/dist
/tmp
/out-tsc

# dependencies
/node_modules

# IDEs and editors
/.idea
.project
.classpath
.c9/
*.launch
.settings/
*.sublime-workspace

# IDE - VSCode
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json

# misc
/.sass-cache
/connect.lock
/coverage
/libpeerconnection.log
npm-debug.log
yarn-error.log
testem.log
/typings

# System Files
.DS_Store
.bash_history
*.swp
*.swo
*.d.ts

*.classpath
*.project
*.settings/
*.classpath
*.project
*.settings/

.vim/bundle
nvim/autoload
nvim/plugged
nvim/doc
nvim/swaps
nvim/colors
dist

/src/mini-testdrive/404.html
/src/mini-testdrive/CNAME
.angulardoc.json
Thumbs.db

reports/*
.nx/cache
.nx/workspace-data

# Created by https://www.toptal.com/developers/gitignore/api/sonarqube
# Edit at https://www.toptal.com/developers/gitignore?templates=sonarqube

### SonarQube ###
# SonarQube ignore files.
#
# https://docs.sonarqube.org/display/SCAN/Analyzing+with+SonarQube+Scanner
# Sonar Scanner working directories
.sonar/
.sonarqube/
.scannerwork/

# http://www.sonarlint.org/commandline/
# SonarLint working directories, configuration files (including credentials)
.sonarlint/

# End of https://www.toptal.com/developers/gitignore/api/sonarqube

.env
4 changes: 4 additions & 0 deletions .husky/commit-msg
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"

npx --no-install commitlint --edit $1
4 changes: 4 additions & 0 deletions .husky/pre-commit
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"

npx --no-install pretty-quick --staged
1 change: 1 addition & 0 deletions .nvmrc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
20
5 changes: 5 additions & 0 deletions .prettierignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# Add files here to ignore them from prettier formatting
/dist
/coverage
/.nx/cache
/.nx/workspace-data
4 changes: 4 additions & 0 deletions .prettierrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"singleQuote": true,
"arrowParens": "avoid"
}
28 changes: 28 additions & 0 deletions .verdaccio/config.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# path to a directory with all packages
storage: ../tmp/local-registry/storage

# a list of other known repositories we can talk to
uplinks:
npmjs:
url: https://registry.npmjs.org/
maxage: 60m

packages:
'**':
# give all users (including non-authenticated users) full access
# because it is a local registry
access: $all
publish: $all
unpublish: $all

# if package is not available locally, proxy requests to npm registry
proxy: npmjs

# log settings
log:
type: stdout
format: pretty
level: warn

publish:
allow_offline: true # set offline to true to allow publish offline
8 changes: 8 additions & 0 deletions .vscode/extensions.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"recommendations": [
"nrwl.angular-console",
"esbenp.prettier-vscode",
"dbaeumer.vscode-eslint",
"firsttris.vscode-jest-runner"
]
}
3 changes: 3 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"cSpell.ignoreWords": ["circleci", "deployer", "npmjs", "npmrc", "whoami"]
}
22 changes: 11 additions & 11 deletions .vscode/tasks.json
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
{
// See https://go.microsoft.com/fwlink/?LinkId=733558
// for the documentation about the tasks.json format
"version": "2.0.0",
"tasks": [
{
"type": "npm",
"script": "build",
"path": "src/",
"label": "npm build"
}
]
// See https://go.microsoft.com/fwlink/?LinkId=733558
// for the documentation about the tasks.json format
"version": "2.0.0",
"tasks": [
{
"type": "npm",
"script": "build",
"path": "src/",
"label": "npm build"
}
]
}
128 changes: 128 additions & 0 deletions CODE_OF_CONDUCT.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
# Contributor Covenant Code of Conduct

## Our Pledge

We as members, contributors, and leaders pledge to make participation in our
community a harassment-free experience for everyone, regardless of age, body
size, visible or invisible disability, ethnicity, sex characteristics, gender
identity and expression, level of experience, education, socio-economic status,
nationality, personal appearance, race, religion, or sexual identity
and orientation.

We pledge to act and interact in ways that contribute to an open, welcoming,
diverse, inclusive, and healthy community.

## Our Standards

Examples of behavior that contributes to a positive environment for our
community include:

* Demonstrating empathy and kindness toward other people
* Being respectful of differing opinions, viewpoints, and experiences
* Giving and gracefully accepting constructive feedback
* Accepting responsibility and apologizing to those affected by our mistakes,
and learning from the experience
* Focusing on what is best not just for us as individuals, but for the
overall community

Examples of unacceptable behavior include:

* The use of sexualized language or imagery, and sexual attention or
advances of any kind
* Trolling, insulting or derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or email
address, without their explicit permission
* Other conduct which could reasonably be considered inappropriate in a
professional setting

## Enforcement Responsibilities

Community leaders are responsible for clarifying and enforcing our standards of
acceptable behavior and will take appropriate and fair corrective action in
response to any behavior that they deem inappropriate, threatening, offensive,
or harmful.

Community leaders have the right and responsibility to remove, edit, or reject
comments, commits, code, wiki edits, issues, and other contributions that are
not aligned to this Code of Conduct, and will communicate reasons for moderation
decisions when appropriate.

## Scope

This Code of Conduct applies within all community spaces, and also applies when
an individual is officially representing the community in public spaces.
Examples of representing our community include using an official e-mail address,
posting via an official social media account, or acting as an appointed
representative at an online or offline event.

## Enforcement

Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported to the community leaders responsible for enforcement at
https://t.me/dianjuar.
All complaints will be reviewed and investigated promptly and fairly.

All community leaders are obligated to respect the privacy and security of the
reporter of any incident.

## Enforcement Guidelines

Community leaders will follow these Community Impact Guidelines in determining
the consequences for any action they deem in violation of this Code of Conduct:

### 1. Correction

**Community Impact**: Use of inappropriate language or other behavior deemed
unprofessional or unwelcome in the community.

**Consequence**: A private, written warning from community leaders, providing
clarity around the nature of the violation and an explanation of why the
behavior was inappropriate. A public apology may be requested.

### 2. Warning

**Community Impact**: A violation through a single incident or series
of actions.

**Consequence**: A warning with consequences for continued behavior. No
interaction with the people involved, including unsolicited interaction with
those enforcing the Code of Conduct, for a specified period of time. This
includes avoiding interactions in community spaces as well as external channels
like social media. Violating these terms may lead to a temporary or
permanent ban.

### 3. Temporary Ban

**Community Impact**: A serious violation of community standards, including
sustained inappropriate behavior.

**Consequence**: A temporary ban from any sort of interaction or public
communication with the community for a specified period of time. No public or
private interaction with the people involved, including unsolicited interaction
with those enforcing the Code of Conduct, is allowed during this period.
Violating these terms may lead to a permanent ban.

### 4. Permanent Ban

**Community Impact**: Demonstrating a pattern of violation of community
standards, including sustained inappropriate behavior, harassment of an
individual, or aggression toward or disparagement of classes of individuals.

**Consequence**: A permanent ban from any sort of public interaction within
the community.

## Attribution

This Code of Conduct is adapted from the [Contributor Covenant][homepage],
version 2.0, available at
https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.

Community Impact Guidelines were inspired by [Mozilla's code of conduct
enforcement ladder](https://github.com/mozilla/diversity).

[homepage]: https://www.contributor-covenant.org

For answers to common questions about this code of conduct, see the FAQ at
https://www.contributor-covenant.org/faq. Translations are available at
https://www.contributor-covenant.org/translations.
2 changes: 1 addition & 1 deletion LICENSE
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
The MIT License (MIT)

Copyright (c) 2019 Minko Gechev, Johannes Hoppe
Copyright (c) 2019-2021 Diego Juliao, Yossely Mendoza

Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
324 changes: 280 additions & 44 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,75 +1,311 @@
# @angular-schule/ngx-deploy-starter 🚀
# ngx-deploy-npm 🚀 <!-- omit in toc -->

[![NPM version][npm-image]][npm-url]
[![The MIT License](https://img.shields.io/badge/license-MIT-orange.svg?color=blue&style=flat-square)](http://opensource.org/licenses/MIT)
[![NPM donwoads][downloads-image]][npm-url]
[![The MIT License][mit-licence-image]][mit-licence-url]
[![Conventional Commits][conventional-commits-image]][conventional-commits-url]

[![Reliability Rating][sonar-reliability-image]][sonar-link]
[![Security Rating][sonar-security-image]][sonar-link]
[![Maintainability Rating][sonar-maintainability-image]][sonar-link]

![Linux][linux-image]
![macOS][macos-image]
![Windows][windows-image]

[![Publishment Status][publishment-image]][publishment-link]
[![Test nx@next][next-tests-image]][next-tests-link]

<!-- Images -->

[sonar-reliability-image]: https://sonarcloud.io/api/project_badges/measure?project=bikecoders_ngx-deploy-npm&metric=reliability_rating
[sonar-security-image]: https://sonarcloud.io/api/project_badges/measure?project=bikecoders_ngx-deploy-npm&metric=security_rating
[sonar-maintainability-image]: https://sonarcloud.io/api/project_badges/measure?project=bikecoders_ngx-deploy-npm&metric=sqale_rating
[publishment-image]: https://github.com/bikecoders/ngx-deploy-npm/actions/workflows/publishment.yml/badge.svg?branch=main
[npm-image]: https://badge.fury.io/js/ngx-deploy-npm.svg
[mit-licence-image]: https://img.shields.io/badge/license-MIT-orange.svg?color=blue&style=flat
[conventional-commits-image]: https://img.shields.io/badge/Conventional%20Commits-1.0.0-yellow.svg
[downloads-image]: https://img.shields.io/npm/dm/ngx-deploy-npm
[supported-nx-versions]: https://img.shields.io/badge/nx%20supported%20versions-v16-143055
[next-tests-image]: https://github.com/bikecoders/ngx-deploy-npm/actions/workflows/test-nx-next.yml/badge.svg
[linux-image]: https://img.shields.io/badge/Linux-FCC624?style=flat&logo=linux&logoColor=black
[macos-image]: https://img.shields.io/badge/mac%20os-000000?style=flat&logo=macos&logoColor=F0F0F0
[windows-image]: https://img.shields.io/badge/Windows-0078D6?style=flat&logo=windows&logoColor=white

<!-- URLs -->

[sonar-link]: https://sonarcloud.io/summary/new_code?id=bikecoders_ngx-deploy-npm
[publishment-link]: https://github.com/bikecoders/ngx-deploy-npm/actions/workflows/publishment.yml
[npm-url]: https://www.npmjs.com/package/ngx-deploy-npm
[mit-licence-url]: http://opensource.org/licenses/MIT
[conventional-commits-url]: https://conventionalcommits.org
[next-tests-link]: https://github.com/bikecoders/ngx-deploy-npm/actions/workflows/test-nx-next.yml

![Cover Image](docs/cover.png)

## Publish your libraries to NPM with one command <!-- omit in toc -->

**Table of contents:**

- [🚀 Quick Start (local development)](#quick-start-local-development)
- [🚀 Continuous Delivery](#continuous-delivery)
- [📦 Options](#options)
- [install](#install)
- [`--dist-folder-path`](#--dist-folder-path-install)
- [`--project`](#--project)
- [`--access`](#--access-install)
- [deploy](#deploy)
- [`--check-existing`](#--check-existing)
- [`--package-version`](#--package-version)
- [`--tag`](#--tag)
- [`--access`](#--access)
- [`--otp`](#--otp)
- [`--dry-run`](#--dry-run)
- [`--dist-folder-path`](#--dist-folder-path)
- [Compatibility overview with Nx](#compatibility-overview-with-nx)
- [📁 Configuration File](#configuration-file)
- [🧐 Essential considerations](#essential-considerations)
- [Version Generation](#version-generation)
- [🎉 Do you Want to Contribute?](#do-you-want-to-contribute)
- [License](#license)
- [Recognitions](#recognitions)

---

## 🚀 Quick Start (local development) <a name="quick-start-local-development"></a>

1. Add `ngx-deploy-npm` to your project. It will configure all your publishable libraries present in the project:

```bash
npm install --save-dev ngx-deploy-npm
nx generate ngx-deploy-npm:install
```

2. Deploy your library to NPM with all default settings.

```sh
nx deploy your-library --dry-run
```

3. When you are happy with the result, remove the `--dry-run` option

## 🚀 Continuous Delivery <a name="continuous-delivery"></a>

Independently of the CI/CD you are using, you need an NPM token. To do so, you have two methods.

- Via [NPM web page](https://docs.npmjs.com/creating-and-viewing-authentication-tokens)
- Using [`npm token create`](https://docs.npmjs.com/cli/token.html)

### [CircleCI](http://circleci.com) <!-- omit in toc -->

1. Set the env variable
- On your project setting the env variable. Let's call it `NPM_TOKEN`
2. Indicate how to find the token
- Before publishing, we must indicate to npm how to find that token,
do it by creating a step with `run: echo '//registry.npmjs.org/:_authToken=${NPM_TOKEN}' > YOUR_REPO_DIRECTORY/.npmrc`
- Replace `YOUR_REPO_DIRECTORY` for the path of your project,
commonly is `/home/circleci/repo`
3. **(Optional)** check that you are logged
- Creating a step with `run: npm whoami`
- The output should be the username of your npm account
4. Deploy your package

- Create a step with:

```sh
nx deploy your-library
```

5. Enjoy your just-released package 🎉📦

The complete job example is:

```yml
# .circleci/config.yml
jobs:
init-deploy:
executor: my-executor
steps:
- attach_workspace:
at: /home/circleci/repo/
# Set NPM token to be able to publish
- run: echo '//registry.npmjs.org/:_authToken=${NPM_TOKEN}' > /home/circleci/repo/.npmrc
- run: npm whoami
- run: npx nx deploy YOUR_PACKAGE
```

> You can check the steps suggested in the [CircleCI's guide](https://circleci.com/blog/publishing-npm-packages-using-circleci-2-0/)
## 📦 Options <a name="options"></a>
### install
![Banner](docs/ng-deploy-starter-project.jpg)
#### `--dist-folder-path` <a name="--dist-folder-path-install"></a>
## About
- **required**
- Example:
- `nx generate ngx-deploy-npm:install --project=lib-1 --dist-folder-path="dist/libs/lib-1"`
This is a sample project that helps you to implement your own __deployment builder__ (`ng deploy`) for the Angular CLI.
The groundwork of this starter was provided by Minko Gechev's [ngx-gh project](https://github.com/mgechev/ngx-gh).
Indicates the dist folder path. The path where is located the bundle of your library. The path should be relative to the project's root.

This project has the following purposes:
#### `--project`

1. To promote the adoption of `ng deploy`.
2. To clarify various questions and to standardise the experience of the various builders.
- **required**
- Example:
- `nx generate ngx-deploy-npm:install --project=lib-1 --dist-folder-path="dist/libs/lib-1"``lib-1` will be configured. It will create the target deploy with the default options on the project `lib-1`.

We hope for an inspiring discussion, pull requests and questions.
Specify which library should be configured.

**If you don't know `ng deploy` yet, learn more about this command here:
[👉 Blogpost: All you need to know about `ng deploy`](https://angular.schule/blog/2019-08-ng-deploy)**
#### `--access` <a name="--access-install"></a>

## Essential considerations
- **optional**
- Default: `public`
- Example:
- `nx generate ngx-deploy-npm:install --access=restricted --project=lib-1 --dist-folder-path="dist/libs/lib-1"`

There are still differences between the existing builders.
Let's find some rules that everyone agrees with. Here are two proposals.
Tells the registry whether to publish the package as public or restricted. It only applies to scoped packages, which default to restricted. If you don't have a paid account, you must publish with --access public to publish scoped packages.
### 1. A deployment builder must always compile the project before the deployment
### deploy
To reduce the chances to deploy corrupted assets, it's important to build the app right before deploying it. ([source](https://github.com/angular-schule/website-articles/pull/3#discussion_r315802100))
#### `--dist-folder-path`
**Current state:**
Currently there are existing deployment builders that only build in production mode.
This might be not enough.
There is also the approach not to perform the build step at all.
- **required**
- Example:
- `nx deploy --dist-folder-path='dist/libs/my-project'`
**Our suggestion:**
By default, a deployment builder **shall** compile in `production` mode, but it **should** be possible to override the default configuration using the option `--configuration`.
Indicate the dist folder path.
The path must relative to project's root.

Discussion: https://github.com/angular-schule/ngx-deploy-starter/issues/1
#### `--check-existing`

### 2. A deployment builder should have an interactive prompt after the "ng add".
- **optional**
- Example:
- `nx deploy --check-existing=warning`
- `nx deploy --check-existing=error`

To make it easier for the end user to get started, a deployment builder **should** ask for all the mandatory questions immediately after the `ng add`.
The data should be persisted in the `angular.json` file.
Check if the package version already exists before publishing.
If it exists and `--check-existing=warning`, it will skip the publishing and log a warning.
If it exists and `--check-existing=error`, it will throw an error.

**Note:**
This feature is not implemented for this starter yet, but we are looking forward to your support.
#### `--package-version`

Discussion: https://github.com/angular-schule/ngx-deploy-starter/issues/2
- **optional**
- Example:
- `nx deploy --package-version 2.3.4`

### 3. More to come
It's going to put that version on your `package.json` and publish the library with that version on NPM.
What's bothers you about this example?
We appreciate your [feedback](https://github.com/angular-schule/ngx-deploy-starter/issues)!
#### `--tag`
- **optional**
- Default: `latest` (string)
- Example:
- `nx deploy --tag alpha` – Your package will be available for download using that tag, `npm install your-package@alpha` useful for RC versions, alpha, betas.
## How to make your own deploy builder
Registers the published package with the given tag, such that `npm install @` will install this version. By default, `npm publish` updates and `npm install` installs the `latest` tag. See [`npm-dist-tag`](https://docs.npmjs.com/cli/dist-tag) for details about tags.
1. fork this repository
2. adjust the `package.json`
3. search and replace for the string `@angular-schule/ngx-deploy-starter` and `ngx-deploy-starter` and choose your own name.
4. search and replace for the string `to the file system` and name your deploy target.
5. add your deployment code to `src/engine/engine.ts`, take care of the tests
6. follow the instructions from the [contributors README](docs/README_contributors.md) for build, test and publishing.
#### `--access`
- Default: `public` (string)
- Example:
- `nx deploy --access public`
You are free to customise this project according to your needs.
Please keep the spirit of Open Source alive and use the MIT or a compatible license.
Tells the registry whether to publish the package as public or restricted. It only applies to scoped packages, which default to restricted. If you don't have a paid account, you must publish with --access public to publish scoped packages.

#### `--otp`

- **optional**
- Example:
- `nx deploy --otp TOKEN`

If you have two-factor authentication enabled in auth-and-writes mode, you can provide a code from your authenticator.

#### `--registry`

- **optional**
- Example:
- `nx deploy --registry http://localhost:4873`

Configure npm to use any compatible registry you like, and even run your own registry.

#### `--dry-run`

- **optional**
- Default: `false` (boolean)
- Example:
- `nx deploy --dry-run`

For testing: Run through without making any changes. Execute with `--dry-run`, and nothing will happen. It will show a list of the options used on the console.

## Compatibility overview with Nx

| Version | Nx Workspace Version |
| ------- | ------------------------------------------------------------- |
| v8.3.0 | `^20.0.0 \|\| ^19.0.0 \|\| ^18.0.0 \|\| ^17.0.0 \|\| ^16.0.0` |
| v8.2.0 | `^19.0.0 \|\| ^18.0.0 \|\| ^17.0.0 \|\| ^16.0.0` |
| v8.1.0 | `^18.0.0 \|\| ^17.0.0 \|\| ^16.0.0` |
| v8.0.0 | `^17.0.0 \|\| ^16.0.0` |
| v7.1.0 | `^17.0.0 \|\| ^16.0.0` |
| v7.0.1 | `^16.0.0` |

## 📁 Configuration File <a name="configuration-file"></a>

To avoid all these command-line cmd options, you can write down your
configuration in the `workspace.json` file in the `options` attribute
of your deploy project's executor.
Just change the option to lower camel case.
A list of all available options is also available [here](https://github.com/bikecoders/ngx-deploy-npm/blob/main/packages/ngx-deploy-npm/src/executors/deploy/schema.json).
Example:
```sh
nx deploy your-library --tag alpha --access public --dry-run
```
becomes
```json
"deploy": {
"executor": "ngx-deploy-npm:deploy",
"options": {
"tag": "alpha",
"access": "public",
"dryRun": true
}
}
```
Now you can just run `nx deploy YOUR-LIBRARY` without all the options in the command line! 😄
> ℹ️ You can always use the [--dry-run](#dry-run) option to verify if your configuration is correct.
## 🧐 Essential considerations <a name="essential-considerations"></a>
### Version Generation
This deployer doesn't bump or generate a new package version; here, we care about doing one thing well, publish your libs to NPM. You can change the version package at publishment using the [`--package-version`](#--package-version) option.

We strongly recommend using [`@jscutlery/semver`](https://github.com/jscutlery/semver) to generate your package's version based on your commits automatically. When a new version is generated you can specify to publish it using `ngx-deploy-npm`.
For more information go to semver's [documentation](https://github.com/jscutlery/semver#triggering-executors-post-release)

We use `@jscutlery/semver` here on `ngx-deploy-npm` to generate the package's next version, and we use `ngx-deploy-npm` to publish that version to NPM. Yes, it uses itself, take a look by yourself [ngx-deploy-npm/project.json](https://github.com/bikecoders/ngx-deploy-npm/blob/main/packages/ngx-deploy-npm/project.json#L55-L67)
### Only publishable libraries are being configured <!-- omit in toc -->
Only publishable libraries are going to be configured.
## 🎉 Do you Want to Contribute? <a name="do-you-want-to-contribute"></a>
We create a unique document for you to give you through this path.
[Readme for Contributors](./docs/README_contributors.md)
## License
Code released under the [MIT license](LICENSE).
## Recognitions
[npm-url]: https://www.npmjs.com/package/@angular-schule/ngx-deploy-starter
[npm-image]: https://badge.fury.io/js/%40angular-schule%2Fngx-deploy-starter.svg
- 🚀 Initially Powered By [ngx-deploy-starter](https://github.com/angular-schule/ngx-deploy-starter)
26 changes: 26 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
version: '3'

services:
sonar_server:
image: sonarqube
ports:
- '9000:9000'

sonar_cli:
image: sonarsource/sonar-scanner-cli
profiles: ['cli']
volumes:
- ./:/github/workspace
env_file:
- tools/docker/sonarqube/env_vars
command: /bin/bash -c 'sonar-scanner -Dsonar.host.url=$$SONAR_HOST_URL -Dsonar.login=admin -Dsonar.password=$$SONAR_PASSWORD'
# To be able to provide absolute paths on configuration files
working_dir: /github/workspace

change_sonar_server_password:
image: curlimages/curl
volumes:
- ./tools/docker/sonarqube/change_password.sh:/home/curl_user/change_password.sh
env_file:
- tools/docker/sonarqube/env_vars
command: ash -c "/home/curl_user/change_password.sh"
181 changes: 102 additions & 79 deletions docs/README_contributors.md
Original file line number Diff line number Diff line change
@@ -1,129 +1,152 @@
# @angular-schule/ngx-deploy-starter: README for contributors
# README for contributors <!-- omit in toc -->

## How to start <a name="start"></a>
## Table of content <!-- omit in toc -->

tl;dr – execute this:
- [How to start](#how-to-start)
- [Angular workspace](#angular-workspace)
- [Debugging on External Workspaces](#debugging-on-external-workspaces)
- [Option A), the easy one](#option-a-the-easy-one)
- [Option B), the traditional one](#option-b-the-traditional-one)
- [Making a Contribution](#making-a-contribution)
- [E2E test](#e2e-test)
- [Smoke test](#smoke-test)
- [Continuous Inspection (SonarQube)](#continuous-inspection-sonarqube)
- [Test different node versions](#test-different-node-versions)
- [When are my changes going to be public?](#when-are-my-changes-going-to-be-public)

```
cd src
npm i
npm run build
npm test
## How to start

As an essential start, you can start installing the project's dependencies:

```bash
yarn install
```

The development process and project architecture are like any other [Nx Plugin](https://nx.dev/l/a/core-concepts/nx-devkit).

## Local development
The maintainers recommend having some knowledge about:

If you want to try the latest package locally without installing it from NPM, use the following instructions.
This may be useful when you want to try the latest non-published version of this library or you want to make a contribution.
- [Angular Schematics](https://angular.io/guide/schematics) and,
- [Nx Plugins](https://nx.dev/l/n/nx-plugin/overview)

Follow the instructions for [checking and updating the Angular CLI version](#angular-cli) and then link the package.
Watch this video to know pretty much everything about this plugin development; it's highly recommended.

[![Nx Plugin](https://img.youtube.com/vi/fC1-4fAZDP4/0.jpg)](https://www.youtube.com/embed/fC1-4fAZDP4?start=40&end=182)

### 1. Angular CLI
## Angular workspace

1. Install the next version of the Angular CLI.
To test the functionality on an Angular workspace, we need to perform some manual operations

```sh
npm install -g @angular/cli@next
```
1. Build the project

2. Run `ng version`, make sure you have installed Angular CLI v8.3.0-next.0 or greater.
```bash
nx build ngx-deploy-npm
```

3. Update your existing project using the command:
2. Go to the compiled files

```sh
ng update @angular/cli @angular/core --next=true
```bash
cd dist/packages/ngx-deploy-npm
```

3. Create a local version of the package:

### 2. npm link
| `yarn link` | `yalc` |
| :---------- | :-------------- |
| `yarn link` | `npx yalc link` |

Use the following instructions to make `@angular-schule/ngx-deploy-starter` available locally via `npm link`.
4. On your [Angular workspace](https://angular.io/cli/new) and:

1. Clone the project
| `yarn link` (recomended) | `yalc` |
| :------------------------- | :---------------------------- |
| `yarn link ngx-deploy-npm` | `npx yalc add ngx-deploy-npm` |

```sh
git clone https://github.com/angular-schule/ngx-deploy-starter.git
cd ngx-deploy-starter
```
## Debugging on External Workspaces

2. Install the dependencies
There are two ways of debugging:

```sh
cd src
npm install
```
#### Option A), the easy one

3. Build the project:
> **Pre Step:** follow the steps of [yarn link](#angular-workspace) as pre step
>
> ⚠️ Only works on VsCode!
```sh
npm run build
```
1. Place `debugger` statement or a red-point where you want your deployer to stop.
2. Build your project `nx build ngx-deploy-npm`.

On VsCode, create a [_JavaScript Debug Terminal_](https://code.visualstudio.com/docs/nodejs/nodejs-debugging#_javascript-debug-terminal) and execute the command that you want to debug

#### Option B), the traditional one

> **Pre Step:** follow the steps of [yarn link](#angular-workspace) as pre step
4. Create a local npm link:
1. Use your favorite [Inspector Client](https://nodejs.org/de/docs/guides/debugging-getting-started/#inspector-clients) to debug

```sh
cd dist
npm link
2. Now, run your command on debug mode using:

```bash
node --inspect-brk ./node_modules/@nx/cli/bin/nx
```

Read more about the `link` feature in the [official NPM documentation](https://docs.npmjs.com/cli/link).
3. Use your favorite Inspector Client to debug

> This is the standard procedure to debug a NodeJs project. If you need more information, you can read the official Docs of NodeJs to learn more about it.
>
> [https://nodejs.org/de/docs/guides/debugging-getting-started/](https://nodejs.org/de/docs/guides/debugging-getting-started/)
### 3. Adding to an Angular project -- ng add
## Making a Contribution

Once you have completed the previous steps to `npm link` the local copy of `@angular-schule/ngx-deploy-starter`, follow these steps to use it in a local Angular project.
1. Verify the issues. Maybe your problem or request already has been addressed by another member of the community
2. Fork it
3. Create your branch
4. Create your commits using [our guidelines](https://www.conventionalcommits.org/en/v1.0.0/)
- If you need help use `yarn commit`
- We use the commit history to generate the changelog automagically, do your best describing the changes that you introduce 😄. Creating the commit right is essential.
- We encourage the use of Unit Tests for the fixes and new features. Don't you know how to write Unit Tests? Don't let that stop your contribution; we are here to help 👋.
5. Make a PR against `main`
6. Wait for the review
7. Merge and Party 🎉

1. Enter the project directory
## E2E test

```sh
cd your-angular-project
```
We at this project have E2E tests. They are handy to test production-like scenarios and to have confidence in your changes.

2. Add the local version of `@angular-schule/ngx-deploy-starter`.
## Smoke test

```sh
npm link @angular-schule/ngx-deploy-starter
```
We conduct a series of small and pragmatic e2e tests called the **smoke test**. This test is essential for testing the package's core functionality.

3. Now execute the `ng-add` schematic.
Using the smoke tests, we composed a series of more elaborate tests. Such as:

```sh
ng add @angular-schule/ngx-deploy-starter
```
- **Compatibility Observability Test**: Scheduled test to verify daily if our package is working with the `latest` version of nx
- **Backwards Compatibility Test**: To verify if the current changes work correctly with the supported versions of nx that we are committed to maintaining (see [Compatibility overview with Nx
](https://github.com/bikecoders/ngx-deploy-npm?tab=readme-ov-file#compatibility-overview-with-nx)).

4. You can now deploy your angular app to GitHub pages.
It's essential to handle the libraries' build before running these tests. These tests will use whatever is in the dist folder. This is done using the build closest to the actual build on the NPM registry.

```sh
ng deploy
```
## Continuous Inspection (SonarQube)

Which is the same as:
```
ng run your-angular-project:deploy
```
We have continuous inspection for each PR that is made; we use SonarQube for this. It will suggest some changes, detect code smells in your changes and, security recommendations. We encourage implementing the changes that Sonar offers.

5. You can remove the link later by running `npm unlink`
If you are changing the Sonar configuration file is highly recommended to test the changes locally.

To init the server

### 4. Testing
- `npm run sonar:init-server`
To run the analysis
- `npm run sonar:analysis`

Testing is done with [Jest](https://jestjs.io/).
To run the tests:
To inspect the analysis, go to http://localhost:9000. The credentials are `admin` and password `12345`

```sh
cd ngx-deploy-starter/src
npm test
```
## Test different node versions

Here, we run the unit, e2e, and regression tests against different node versions in our CI. We use the [Nx NodeJs Compatibility Matrix](https://nx.dev/nx-api/workspace/documents/nx-nodejs-typescript-version-matrix) to determine which versions should be tested.

The retro compatibility tests are a bit different since we match the Nx Workspace Version with the supported Node versions.

## Publish to NPM
For local development, stick to the one defined in the `.npmrc` file.

```
cd ngx-deploy-starter/src
npm run build
npm run test
npm publish dist --access public
```
## When are my changes going to be public?

The CI handles the publishment of a new version. We use GitHub actions as CI.

When the maintainers integrate your PR to `main`, go to the [main branch actions](https://github.com/bikecoders/ngx-deploy-npm/actions/workflows/publishment.yml) and search for the one that belongs to you. The CI will run some tests, if they pass, the next job that publishes your introduced changes will be **on hold** waiting for approval; once the maintainers approve the launching, your changes will be packed and posted to NPM.
Binary file added docs/cover.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/cover.xcf
Binary file not shown.
Binary file removed docs/ng-deploy-starter-project.jpg
Binary file not shown.
86 changes: 86 additions & 0 deletions docs/ngx-deploy-npm-logo.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
40 changes: 40 additions & 0 deletions eslint.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
const nx = require('@nx/eslint-plugin');

module.exports = [
{
files: ['**/*.json'],
// Override or add rules here
rules: {},
languageOptions: { parser: require('jsonc-eslint-parser') },
},

...nx.configs['flat/base'],
...nx.configs['flat/typescript'],
...nx.configs['flat/javascript'],
{
ignores: ['**/dist'],
},
{
files: ['**/*.ts', '**/*.tsx', '**/*.js', '**/*.jsx'],
rules: {
'@nx/enforce-module-boundaries': [
'error',
{
enforceBuildableLibDependency: true,
allow: ['^.*/eslint(\\.base)?\\.config\\.[cm]?js$'],
depConstraints: [
{
sourceTag: '*',
onlyDependOnLibsWithTags: ['*'],
},
],
},
],
},
},
{
files: ['**/*.ts', '**/*.tsx', '**/*.js', '**/*.jsx'],
// Override or add rules here
rules: {},
},
];
5 changes: 5 additions & 0 deletions jest.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { getJestProjectsAsync } from '@nx/jest';

export default async () => ({
projects: await getJestProjectsAsync(),
});
3 changes: 3 additions & 0 deletions jest.preset.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
const nxPreset = require('@nx/jest/preset').default;

module.exports = { ...nxPreset };
48 changes: 48 additions & 0 deletions nx.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
{
"$schema": "./node_modules/nx/schemas/nx-schema.json",
"namedInputs": {
"default": ["{projectRoot}/**/*", "sharedGlobals"],
"production": [
"default",
"!{projectRoot}/.eslintrc.json",
"!{projectRoot}/eslint.config.js",
"!{projectRoot}/**/?(*.)+(spec|test).[jt]s?(x)?(.snap)",
"!{projectRoot}/tsconfig.spec.json",
"!{projectRoot}/jest.config.[jt]s",
"!{projectRoot}/src/test-setup.[jt]s",
"!{projectRoot}/test-setup.[jt]s"
],
"sharedGlobals": []
},
"targetDefaults": {
"@nx/js:tsc": {
"cache": true,
"dependsOn": ["^build"],
"inputs": ["production", "^production"]
},
"@nx/eslint:lint": {
"cache": true,
"inputs": [
"default",
"{workspaceRoot}/.eslintrc.json",
"{workspaceRoot}/.eslintignore",
"{workspaceRoot}/eslint.config.js"
]
},
"@nx/jest:jest": {
"cache": true,
"inputs": ["default", "^production", "{workspaceRoot}/jest.preset.js"],
"options": {
"passWithNoTests": true
},
"configurations": {
"ci": {
"ci": true,
"codeCoverage": true
}
}
}
},
"defaultBase": "main",
"useLegacyCache": true
}
14,925 changes: 14,925 additions & 0 deletions package-lock.json

Large diffs are not rendered by default.

70 changes: 70 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
{
"name": "bikecoders",
"version": "0.0.0",
"license": "MIT",
"scripts": {
"build": "nx build ngx-deploy-npm",
"test": "nx test ngx-deploy-npm",
"release": "nx version ngx-deploy-npm",
"commit": "cz",
"prepare": "husky install",
"sonar:init-server": "docker-compose up",
"sonar:analysis": "docker-compose run --rm sonar_cli"
},
"private": true,
"devDependencies": {
"@commitlint/cli": "18.6.1",
"@commitlint/config-conventional": "18.6.3",
"@eslint/js": "^9.8.0",
"@jscutlery/semver": "5.3.1",
"@nx/devkit": "20.2.0",
"@nx/eslint": "20.2.0",
"@nx/eslint-plugin": "20.2.0",
"@nx/jest": "20.2.0",
"@nx/js": "20.2.0",
"@nx/plugin": "20.2.0",
"@nx/workspace": "20.2.0",
"@swc-node/register": "~1.9.1",
"@swc/core": "~1.5.7",
"@swc/helpers": "~0.5.11",
"@types/jest": "^29.5.12",
"@types/node": "^20.14.8",
"commitizen": "4.3.1",
"create-nx-workspace": "20.2.0",
"cz-conventional-changelog": "3.3.0",
"eslint": "^9.8.0",
"eslint-config-prettier": "9.0.0",
"husky": "8.0.3",
"jest": "^29.7.0",
"jest-environment-jsdom": "^29.7.0",
"nx": "20.2.0",
"prettier": "2.8.4",
"pretty-quick": "^3.1.3",
"ts-jest": "29.1.0",
"ts-node": "10.9.1",
"typescript": "5.5.4",
"typescript-eslint": "^8.0.0",
"verdaccio": "^5.0.4"
},
"dependencies": {
"tslib": "^2.3.0"
},
"config": {
"commitizen": {
"path": "cz-conventional-changelog"
}
},
"commitlint": {
"extends": [
"@commitlint/config-conventional"
]
},
"nx": {
"includedScripts": []
},
"overrides": {
"@jscutlery/semver": {
"@nx/devkit": "^20.0.0"
}
}
}
3 changes: 3 additions & 0 deletions packages/ngx-deploy-npm-e2e/.env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
NGX_DEPLOY_NPM_E2E__NX_VERSION=previous
NGX_DEPLOY_NPM_E2E__PROJECT_NAME=test-project
NGX_DEPLOY_NPM_E2E__NO_TEAR_DOWN=true
20 changes: 20 additions & 0 deletions packages/ngx-deploy-npm-e2e/eslint.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
const baseConfig = require('../../eslint.config.js');

module.exports = [
...baseConfig,
{
files: ['**/*.ts', '**/*.tsx', '**/*.js', '**/*.jsx'],
// Override or add rules here
rules: {},
},
{
files: ['**/*.ts', '**/*.tsx'],
// Override or add rules here
rules: {},
},
{
files: ['**/*.js', '**/*.jsx'],
// Override or add rules here
rules: {},
},
];
11 changes: 11 additions & 0 deletions packages/ngx-deploy-npm-e2e/jest.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
export default {
displayName: 'ngx-deploy-npm-e2e',
preset: '../../jest.preset.js',
transform: {
'^.+\\.[tj]s$': ['ts-jest', { tsconfig: '<rootDir>/tsconfig.spec.json' }],
},
moduleFileExtensions: ['ts', 'js', 'html'],
coverageDirectory: '../../coverage/packages/ngx-deploy-npm-e2e',
globalSetup: '../../tools/scripts/start-local-registry.ts',
globalTeardown: '../../tools/scripts/stop-local-registry.ts',
};
31 changes: 31 additions & 0 deletions packages/ngx-deploy-npm-e2e/project.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
{
"name": "ngx-deploy-npm-e2e",
"$schema": "../../node_modules/nx/schemas/project-schema.json",
"projectType": "application",
"sourceRoot": "packages/ngx-deploy-npm-e2e/src",
"implicitDependencies": ["ngx-deploy-npm"],
"targets": {
"e2e": {
"executor": "@nx/jest:jest",
"outputs": ["{workspaceRoot}/coverage/{projectRoot}"],
"options": {
"jestConfig": "packages/ngx-deploy-npm-e2e/jest.config.ts",
"runInBand": true
},
"dependsOn": ["^build"]
},
"smoke": {
"cache": false,
"executor": "@nx/jest:jest",
"outputs": ["{workspaceRoot}/coverage/{projectRoot}"],
"options": {
"testPathPattern": [".*smoke.*\\.spec\\.ts$"],
"jestConfig": "packages/ngx-deploy-npm-e2e/jest.config.ts",
"runInBand": true
}
},
"lint": {
"executor": "@nx/eslint:lint"
}
}
}
80 changes: 80 additions & 0 deletions packages/ngx-deploy-npm-e2e/src/install.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import { TargetConfiguration } from '@nx/devkit';
import { uniq } from '@nx/plugin/testing';

import { DeployExecutorOptions } from 'bikecoders/ngx-deploy-npm';
import { npmAccess } from 'bikecoders/ngx-deploy-npm';
import { setup } from './utils';

describe('install', () => {
const expectedTarget = ({
projectName,
isBuildable = true,
access = npmAccess.public,
customDistFolderPath,
}: {
projectName: string;
isBuildable?: boolean;
access?: npmAccess;
customDistFolderPath?: string;
}): TargetConfiguration<DeployExecutorOptions> => {
const target: TargetConfiguration<DeployExecutorOptions> = {
executor: 'ngx-deploy-npm:deploy',
options: {
distFolderPath: customDistFolderPath || `dist/packages/${projectName}`,
access,
},
};

if (isBuildable) {
target.dependsOn = ['build'];
}

return target;
};

it('should modify the workspace only for the indicated libs', async () => {
const { processedLibs, tearDown } = await setup([
{ name: uniq('node-lib1'), generator: '@nx/node' },
{ name: uniq('node-lib2'), generator: '@nx/node' },
{
name: uniq('node-resctricted'),
installOptions: {
access: npmAccess.restricted,
},
generator: '@nx/node',
},
{
name: uniq('node-lib-not-set'),
skipInstall: true,
generator: '@nx/node',
},
{ name: uniq('small-lib'), generator: 'minimal' },
]);
const [publicLib, publicLib2, restrictedLib, libNOTSet, smallLib] =
processedLibs;

expect(publicLib.workspace.targets?.deploy).toEqual(
expectedTarget({ projectName: publicLib.name })
);
expect(publicLib2.workspace.targets?.deploy).toEqual(
expectedTarget({ projectName: publicLib2.name })
);
expect(restrictedLib.workspace.targets?.deploy).toEqual(
expectedTarget({
projectName: restrictedLib.name,
access: npmAccess.restricted,
})
);
expect(libNOTSet.workspace.targets?.deploy).toEqual(undefined);

expect(smallLib.workspace.targets?.deploy).toEqual(
expectedTarget({
projectName: smallLib.name,
customDistFolderPath: smallLib.workspace.sourceRoot,
isBuildable: false,
})
);

return tearDown();
}, 180000);
});
11 changes: 11 additions & 0 deletions packages/ngx-deploy-npm-e2e/src/package-installation.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { setup } from './utils';

describe('ngx-deploy-npm', () => {
it('should be installed', async () => {
const { tearDown, executeCommand } = await setup([]);

expect(() => executeCommand('npm ls ngx-deploy-npm')).not.toThrow();

return tearDown();
}, 120000);
});
22 changes: 22 additions & 0 deletions packages/ngx-deploy-npm-e2e/src/publish-minimal-lib.smoke.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { uniq } from '@nx/plugin/testing';
import { setup } from './utils';

describe('Minimal Project', () => {
it('should publish the lib', async () => {
const { processedLibs, tearDown, executeCommand } = await setup([
{ name: uniq('minimal-lib'), generator: 'minimal' },
]);
const [uniqLibName] = processedLibs;

executeCommand(
`npx nx deploy ${uniqLibName.name} --tag="e2e" --registry=http://localhost:4873 --packageVersion=0.0.0`
);

expect(() => {
executeCommand(`npm view ${uniqLibName.npmPackageName}@0.0.0`);
executeCommand(`npm view ${uniqLibName.npmPackageName}@e2e`);
}).not.toThrow();

return tearDown();
}, 120000);
});
55 changes: 55 additions & 0 deletions packages/ngx-deploy-npm-e2e/src/publish.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import { uniq } from '@nx/plugin/testing';
import { setup } from './utils';

describe('Publish', () => {
test.each([
{
name: uniq('angular-lib'),
generator: '@nx/angular',
extraOptions: '--style css',
},
{
name: uniq('node-lib'),
generator: '@nx/node',
},
])(
'should publish with $generator lib',
async libConfig => {
const { executeCommand, tearDown, processedLibs } = await setup([
libConfig,
]);
const [lib] = processedLibs;

executeCommand(
`npx nx deploy ${lib.name} --registry=http://localhost:4873 --tag="e2e" --packageVersion=0.0.0`
);

expect(() => {
executeCommand(`npm view ${lib.npmPackageName}@0.0.0`);
executeCommand(`npm view ${lib.npmPackageName}@e2e`);
}).not.toThrow();

return tearDown();
},
120000 * 2
);

it('should NOT publish because it already exists', async () => {
const { processedLibs, tearDown, executeCommand } = await setup([
{ name: uniq('minimal-lib'), generator: 'minimal' },
]);
const [uniqLibName] = processedLibs;

executeCommand(
`npx nx deploy ${uniqLibName.name} --tag="e2e" --registry=http://localhost:4873 --packageVersion=0.0.0 --checkExisting="error"`
);

expect(() => {
executeCommand(
`npx nx deploy ${uniqLibName.name} --tag="e2e" --registry=http://localhost:4873 --packageVersion=0.0.0 --checkExisting="error"`
);
}).toThrow();

return tearDown();
}, 120000);
});
224 changes: 224 additions & 0 deletions packages/ngx-deploy-npm-e2e/src/utils/basic-setup.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,224 @@
import * as fs from 'fs';
import { dirname, join } from 'path';
import { execSync } from 'child_process';

import { ProjectConfiguration } from '@nx/devkit';
import { readJson } from '@nx/plugin/testing';
import { logger } from '@nx/devkit';

import { InstallGeneratorOptions } from 'bikecoders/ngx-deploy-npm';
import { getNxWorkspaceVersion } from './get-nx-workspace-version';
import {
generateLib,
initNgxDeployNPMProject,
installDependencies,
installNgxDeployNPMProject,
} from '.';

export const buildPackageProjectRoot = (libName: string) =>
`packages/${libName}`;

const executeCommandFactory =
(projectDirectory: string) => (command: string) => {
let output: string;

logger.verbose(`Executing command: ${command}`);

try {
output = execSync(command, {
stdio: 'inherit',
cwd: projectDirectory,
encoding: 'utf-8',
env: process.env,
});
} catch (error) {
console.error(`Error executing command: ${command}`);
if (error instanceof Error) {
if ('stderr' in error && error.stderr) {
console.error(`stderr: ${error.stderr}`);
}
if (error.message) {
console.error(`message: ${error.message}`);
}
} else {
console.error(`Unknown error: ${error}`);
}
throw error;
}

return output;
};

export const setup = async (
libs: {
name: string;
generator: 'minimal' | string;
installOptions?: Omit<
Partial<InstallGeneratorOptions>,
'distFolderPath' | 'project'
>;
skipInstall?: boolean;
extraOptions?: string;
distFolderPath?: string;
}[]
) => {
const projectDirectory = await createTestProject();
const executeCommand = executeCommandFactory(projectDirectory);

initNgxDeployNPMProject(executeCommand);

// Install dependencies only once
const generators = new Set(
libs
.map(({ generator }) => generator)
.filter(generator => generator !== 'minimal')
);
generators.forEach(dependency => {
installDependencies(executeCommand, dependency);
});

// Init libs
await Promise.all(
libs.map(async ({ name, generator, extraOptions = '' }) => {
if (generator === 'minimal') {
await createMinimalLib(projectDirectory, name);
} else {
generateLib({
nxPlugin: generator,
executeCommand,
libName: name,
extraOptions: `--directory="${buildPackageProjectRoot(
name
)}" ${extraOptions}`,
});
}
})
);

// Install ngx-deploy-npm
libs
.filter(({ skipInstall }) => !!skipInstall === false)
.forEach(({ name, installOptions, generator }) => {
installNgxDeployNPMProject(executeCommand, {
project: name,
distFolderPath:
generator === 'minimal'
? buildPackageProjectRoot(name)
: `dist/${buildPackageProjectRoot(name)}`,
access: installOptions?.access || 'public',
...installOptions,
});
});

const processedLibs: {
name: string;
workspace: ProjectConfiguration;
npmPackageName: string;
}[] = libs.map(({ name }) => ({
name: name,
workspace: readJson(
`${projectDirectory}/${buildPackageProjectRoot(name)}/project.json`
),
npmPackageName: readJson(
`${projectDirectory}/${buildPackageProjectRoot(name)}/package.json`
).name,
}));

return {
processedLibs,
projectDirectory,
tearDown: async () => {
if (process.env.NGX_DEPLOY_NPM_E2E__NO_TEAR_DOWN !== 'true') {
await fs.promises.rm(projectDirectory, {
recursive: true,
force: true,
});
}

return Promise.resolve();
},
executeCommand,
};
};

async function createMinimalLib(projectDirectory: string, libName: string) {
// Create Lib
const libRootAbsolutePath = join(
projectDirectory,
buildPackageProjectRoot(libName)
);
const libRoot = buildPackageProjectRoot(libName);

// Create the lib folder
await fs.promises.mkdir(libRootAbsolutePath, {
recursive: true,
});

const createProjectJsonPromise = fs.promises.writeFile(
join(libRootAbsolutePath, 'project.json'),
generateProjectJSON(libName, libRoot),
'utf8'
);
const createPackageJsonPromise = fs.promises.writeFile(
join(libRootAbsolutePath, 'package.json'),
generatePackageJSON(libName),
'utf8'
);
const createUniqueFilePromise = fs.promises.writeFile(
join(libRootAbsolutePath, 'hello-world.js'),
"console.log('Hello World!');",
'utf8'
);
await Promise.all([
createProjectJsonPromise,
createPackageJsonPromise,
createUniqueFilePromise,
]);

return { libRoot };

function generateProjectJSON(projectName: string, libRoot: string): string {
const content = {
name: projectName,
$schema: '../../node_modules/nx/schemas/project-schema.json',
projectType: 'library',
sourceRoot: libRoot,
};

return JSON.stringify(content, null, 2);
}

function generatePackageJSON(projectName: string): string {
const content = {
name: `@mock-domain/${projectName}`,
description: 'Minimal LIb',
version: '1.0.0',
};

return JSON.stringify(content, null, 2);
}
}

/**
* Creates a test project with create-nx-workspace and installs the plugin
* @returns The directory where the test project was created
*/
async function createTestProject() {
const projectName =
process.env.NGX_DEPLOY_NPM_E2E__PROJECT_NAME || 'test-project';
const projectDirectory = join(process.cwd(), 'tmp', projectName);

// Ensure projectDirectory is empty
await fs.promises.rm(projectDirectory, {
recursive: true,
force: true,
});
await fs.promises.mkdir(dirname(projectDirectory), {
recursive: true,
});

const command = `npx --yes create-nx-workspace@${getNxWorkspaceVersion()} ${projectName} --preset npm --nxCloud=skip --no-interactive`;
executeCommandFactory(dirname(projectDirectory))(command);

return projectDirectory;
}
24 changes: 24 additions & 0 deletions packages/ngx-deploy-npm-e2e/src/utils/generate-lib.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
type Options = {
nxPlugin: string;
libName: string;
executeCommand: (command: string) => void;
extraOptions?: string;
generator?: string;
setPublishableOption?: boolean;
};

export function generateLib({
nxPlugin,
libName,
executeCommand,
extraOptions,
generator = 'lib',
setPublishableOption = true,
}: Options) {
const publishableOption = setPublishableOption ? '--publishable' : '';
const extraOptionsNormalized = extraOptions ? extraOptions : '';

executeCommand(
`npx nx generate ${nxPlugin}:${generator} --name ${libName} ${publishableOption} --importPath ${libName} ${extraOptionsNormalized}`
);
}
13 changes: 13 additions & 0 deletions packages/ngx-deploy-npm-e2e/src/utils/get-nx-workspace-version.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { readJson } from '@nx/plugin/testing';

export const getNxWorkspaceVersion = () => {
const nxVersion = process.env.NGX_DEPLOY_NPM_E2E__NX_VERSION;

if (nxVersion) {
return nxVersion;
} else {
return readJson(`${process.cwd()}/package.json`).devDependencies[
'@nx/workspace'
];
}
};
5 changes: 5 additions & 0 deletions packages/ngx-deploy-npm-e2e/src/utils/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export * from './generate-lib';
export * from './install-deps';
export * from './utils-ngx-deploy-npm';
export * from './get-nx-workspace-version';
export * from './basic-setup';
11 changes: 11 additions & 0 deletions packages/ngx-deploy-npm-e2e/src/utils/install-deps.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { getNxWorkspaceVersion } from './get-nx-workspace-version';

export function installDependencies(
executeCommand: (command: string) => string,
nxPlugin: string
) {
const packageToInstall = `${nxPlugin}@${getNxWorkspaceVersion()}`;

executeCommand(`npm add -D ${packageToInstall}`);
executeCommand(`npx nx g ${nxPlugin}:init`);
}
27 changes: 27 additions & 0 deletions packages/ngx-deploy-npm-e2e/src/utils/utils-ngx-deploy-npm.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { InstallGeneratorOptions } from 'bikecoders/ngx-deploy-npm';
import { getNxWorkspaceVersion } from './get-nx-workspace-version';

export function initNgxDeployNPMProject(
executeCommand: (command: string) => void
) {
executeCommand(`npm install -D @nx/devkit@${getNxWorkspaceVersion()}`);
executeCommand(`npm install -D ngx-deploy-npm@e2e`);
}

export function installNgxDeployNPMProject(
executeCommand: (command: string) => void,
options: InstallGeneratorOptions
) {
const optionGenerator = <T>(
optionaName: keyof InstallGeneratorOptions,
value?: T
) => (value ? `--${optionaName}="${value}"` : '');

const optionsParsed = Object.entries(options ?? {})
.map(([key, value]) =>
optionGenerator(key as keyof InstallGeneratorOptions, value)
)
.join(' ');

executeCommand(`npx nx generate ngx-deploy-npm:install ${optionsParsed}`);
}
10 changes: 10 additions & 0 deletions packages/ngx-deploy-npm-e2e/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"extends": "../../tsconfig.base.json",
"files": [],
"include": [],
"references": [
{
"path": "./tsconfig.spec.json"
}
]
}
15 changes: 15 additions & 0 deletions packages/ngx-deploy-npm-e2e/tsconfig.spec.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"outDir": "../../dist/out-tsc",
"module": "commonjs",
"types": ["jest", "node"]
},
"include": [
"jest.config.ts",
"src/**/*.test.ts",
"src/**/*.spec.ts",
"src/**/*.d.ts",
"../../tools/scripts/scripts.d.ts"
]
}
10 changes: 10 additions & 0 deletions packages/ngx-deploy-npm/.babelrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"presets": [
[
"@nx/js/babel",
{
"useBuiltIns": "usage"
}
]
]
}
482 changes: 482 additions & 0 deletions packages/ngx-deploy-npm/CHANGELOG.md

Large diffs are not rendered by default.

25 changes: 25 additions & 0 deletions packages/ngx-deploy-npm/eslint.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
const baseConfig = require('../../eslint.config.js');

module.exports = [
...baseConfig,
{
files: ['**/*.ts', '**/*.tsx', '**/*.js', '**/*.jsx'],
// Override or add rules here
rules: {},
},
{
files: ['**/*.ts', '**/*.tsx'],
// Override or add rules here
rules: {},
},
{
files: ['**/*.js', '**/*.jsx'],
// Override or add rules here
rules: {},
},
{
files: ['**/*.json'],
rules: { '@nx/dependency-checks': 'error' },
languageOptions: { parser: require('jsonc-eslint-parser') },
},
];
10 changes: 10 additions & 0 deletions packages/ngx-deploy-npm/executors.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"$schema": "http://json-schema.org/schema",
"executors": {
"deploy": {
"implementation": "./src/executors/deploy/executor",
"schema": "./src/executors/deploy/schema.json",
"description": "Publish your libraries to NPM with just one command"
}
}
}
12 changes: 12 additions & 0 deletions packages/ngx-deploy-npm/generators.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"$schema": "http://json-schema.org/schema",
"name": "ngx-deploy-npm",
"version": "0.0.1",
"generators": {
"install": {
"factory": "./src/generators/install/generator",
"schema": "./src/generators/install/schema.json",
"description": "install generator"
}
}
}
16 changes: 16 additions & 0 deletions packages/ngx-deploy-npm/jest.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
export default {
displayName: 'ngx-deploy-npm',
preset: '../../jest.preset.js',
testEnvironment: 'node',
transform: {
'^.+\\.[tj]sx?$': [
'ts-jest',
{
tsconfig: '<rootDir>/tsconfig.spec.json',
},
],
},
moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx'],
coverageDirectory: '../../coverage/packages/ngx-deploy-npm',
coverageReporters: [['lcov', { projectRoot: 'packages/ngx-deploy-npm' }]],
};
14 changes: 14 additions & 0 deletions packages/ngx-deploy-npm/migrations.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"generators": {
"write-dist-folder-path-on-deploy-target-options": {
"version": "8.0.0",
"description": "Sets the distFolderPath option on the deploy target. distFolderPath is now required.",
"implementation": "./src/migrations/8.0.0/write-dist-folder-path-on-deploy-target-options"
},
"replace-buildtarget-for-depends-on": {
"version": "8.0.0",
"description": "Remove the option `buildTarget` and `noBuild` from the deploy target. If the option `buildTarget` was set, the migration will create a new target to build the library with the desired configuration and create a `dependsOn` on the deploy target to build the library using the just-created target.",
"implementation": "./src/migrations/8.0.0/replace-buildtarget-for-depends-on"
}
}
}
44 changes: 44 additions & 0 deletions packages/ngx-deploy-npm/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
{
"name": "ngx-deploy-npm",
"version": "8.4.0",
"description": "Publish your libraries to NPM with just one command",
"main": "src/index.js",
"generators": "./generators.json",
"executors": "./executors.json",
"engines": {
"node": ">=16.0.0"
},
"peerDependencies": {
"@nx/devkit": ">=16.0.0 <=21.0.0",
"tslib": "^2.3.0"
},
"repository": {
"type": "git",
"url": "git+https://github.com/bikecoders/ngx-deploy-npm.git"
},
"keywords": [
"npm",
"npm-package",
"nx",
"nx-plugin",
"npm-publish",
"cli",
"deploy",
"publish"
],
"author": {
"name": "Diego Juliao",
"email": "dianjuar@gmail.com"
},
"contributors": [
"Yossely Mendoza <yossely7@gmail.com>"
],
"license": "MIT",
"bugs": {
"url": "https://github.com/bikecoders/ngx-deploy-npm/issues"
},
"homepage": "https://github.com/bikecoders/ngx-deploy-npm/#readme",
"nx-migrations": {
"migrations": "./migrations.json"
}
}
98 changes: 98 additions & 0 deletions packages/ngx-deploy-npm/project.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
{
"name": "ngx-deploy-npm",
"$schema": "../../node_modules/nx/schemas/project-schema.json",
"sourceRoot": "packages/ngx-deploy-npm/src",
"projectType": "library",
"tags": [],
"targets": {
"lint": {
"executor": "@nx/eslint:lint"
},
"lint-report": {
"executor": "nx:run-commands",
"options": {
"command": "npx nx lint ngx-deploy-npm --outputFile reports/ngx-deploy-npm/lint-report.info --format=./tools/sonarqube-linter-reporter.js || true"
}
},
"test": {
"executor": "@nx/jest:jest",
"outputs": ["{workspaceRoot}/coverage/packages/ngx-deploy-npm"],
"options": {
"jestConfig": "packages/ngx-deploy-npm/jest.config.ts"
}
},
"build": {
"executor": "@nx/js:tsc",
"outputs": ["{options.outputPath}"],
"options": {
"outputPath": "dist/packages/ngx-deploy-npm",
"tsConfig": "packages/ngx-deploy-npm/tsconfig.lib.json",
"packageJson": "packages/ngx-deploy-npm/package.json",
"main": "packages/ngx-deploy-npm/src/index.ts",
"assets": [
"README.md",
"LICENSE",
{
"input": "./packages/ngx-deploy-npm/src",
"glob": "**/!(*.ts)",
"output": "./src"
},
{
"input": "docs",
"glob": "**/(*.png|*.jpg)",
"output": "./docs"
},
{
"input": "./packages/ngx-deploy-npm/src",
"glob": "**/*.d.ts",
"output": "./src"
},
{
"input": "./packages/ngx-deploy-npm",
"glob": "generators.json",
"output": "."
},
{
"input": "./packages/ngx-deploy-npm",
"glob": "executors.json",
"output": "."
},
{
"input": "./packages/ngx-deploy-npm",
"glob": "migrations.json",
"output": "."
}
]
}
},
"deploy": {
"executor": "./dist/packages/ngx-deploy-npm:deploy",
"options": {
"distFolderPath": "dist/packages/ngx-deploy-npm",
"access": "public"
},
"dependsOn": ["build"]
},
"deploy:without-build": {
"executor": "./dist/packages/ngx-deploy-npm:deploy",
"options": {
"distFolderPath": "dist/packages/ngx-deploy-npm",
"access": "public"
}
},
"github": {
"executor": "@jscutlery/semver:github",
"options": {
"tag": "{tag}",
"notes": "{notes}"
}
},
"version": {
"executor": "@jscutlery/semver:version",
"options": {
"postTargets": ["build", "deploy"],
"versionTagPrefix": "v"
}
}
}
}
16 changes: 16 additions & 0 deletions packages/ngx-deploy-npm/src/__mocks__/@nx/devkit/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { logger as originalLogger } from '@nx/devkit';

const logger: Record<keyof typeof originalLogger, jest.Mock> = {
warn: jest.fn(),
error: jest.fn(),
info: jest.fn(),
log: jest.fn(),
debug: jest.fn(),
fatal: jest.fn(),
verbose: jest.fn(),
};

module.exports = {
...jest.requireActual('@nx/devkit'),
logger,
};
41 changes: 41 additions & 0 deletions packages/ngx-deploy-npm/src/__mocks__/child_process/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import * as OriginalChildProcessModule from 'child_process';

const listOfChildProcess: Record<string, ReturnType<typeof spawnMock>> = {};

function createReadable() {
const observers: Record<string, (data: unknown) => void> = {};

return {
on: (event: string, listener: (data: unknown) => void) => {
observers[event] = listener;
},
emit: (event: string, data: unknown) => {
observers[event](data);
},
};
}

function spawnMock(command: string) {
const childProcess = {
stdout: createReadable(),
stderr: createReadable(),
...createReadable(),
};

listOfChildProcess[command] = childProcess;

return childProcess;
}

const mockedChildProcessModule = {
...(jest.requireActual('child_process') as typeof OriginalChildProcessModule),
spawn: jest.fn().mockImplementation(spawnMock),
listOfChildProcess,
};

module.exports = mockedChildProcessModule;

export type SpawnMock = {
spawn: typeof spawnMock;
listOfChildProcess: typeof listOfChildProcess;
};
116 changes: 116 additions & 0 deletions packages/ngx-deploy-npm/src/__mocks__/mocks.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
import { ProjectConfiguration } from '@nx/devkit';

export const getLib = (libName: string): ProjectConfiguration => {
return {
root: `libs/${libName}`,
name: libName,
sourceRoot: `libs/${libName}/src`,
projectType: 'library',
tags: [],
targets: {
build: {
executor: '@mocks/compiler:rollup',
outputs: ['{options.outputPath}'],
options: {
outputPath: `dist/libs/${libName}`,
},
},
...mockLintTarget,
...mockTestTarget,
},
};
};

export const getTargetlessLib = (libName: string): ProjectConfiguration => {
return {
root: `libs/${libName}`,
name: libName,
sourceRoot: `libs/${libName}/src`,
projectType: 'library',
tags: [],
};
};

// ? should we remove this since we are no longer building the library?
export const getLibWithoutBuildTarget = (
libName: string
): ProjectConfiguration => {
return {
root: `libs/${libName}`,
name: libName,
sourceRoot: `libs/${libName}/src`,
projectType: 'library',
tags: [],
targets: {
...mockLintTarget,
...mockTestTarget,
},
};
};

export const getApplication = (appName: string): ProjectConfiguration => {
return {
projectType: 'application',
root: `apps/${appName}`,
name: appName,
sourceRoot: `apps/${appName}/src`,
targets: {
build: {
executor: '@mocks/build:browser',
outputs: ['{options.outputPath}'],
options: {
outputPath: `dist/apps/${appName}`,
index: `apps/${appName}/src/index.html`,
main: `apps/${appName}/src/main.ts`,
tsConfig: `apps/${appName}/tsconfig.app.json`,
scripts: [],
},
configurations: {
production: {
foo: 'bar',
randomOptions: true,
production: true,
},
development: {
foo: 'bar',
randomOptions: false,
production: false,
sourceMap: true,
},
},
defaultConfiguration: 'production',
},
serve: {
executor: '@mocks/build:dev-server',
configurations: {
production: {
browserTarget: `${appName}:build:production`,
},
development: {
browserTarget: `${appName}:build:development`,
},
},
defaultConfiguration: 'development',
},
...mockLintTarget,
...mockTestTarget,
},
tags: [],
};
};

export const mockProjectRoot = 'some/fake/root/folder';
export const mockProjectDist = (projectName = 'project') =>
`dist/lib/${projectName}`;

const mockLintTarget = {
lint: {
executor: '@mocks/linter:eslint',
},
};

const mockTestTarget = {
test: {
executor: '@mocks/test:simple-test',
},
};
1 change: 1 addition & 0 deletions packages/ngx-deploy-npm/src/core/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './npm-access.enum';
4 changes: 4 additions & 0 deletions packages/ngx-deploy-npm/src/core/npm-access.enum.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export enum npmAccess {
public = 'public',
restricted = 'restricted',
}
45 changes: 45 additions & 0 deletions packages/ngx-deploy-npm/src/executors/deploy/actions.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import * as nxDevKit from '@nx/devkit';
import * as path from 'path';

import deploy from './actions';
import { mockProjectRoot } from '../../__mocks__/mocks';
import { DeployExecutorOptions } from './schema';

describe('Deploy', () => {
const setup = () => {
const PROJECT = 'RANDOM-PROJECT';
const mockEngine = {
run: jest.fn().mockImplementation(() => () => Promise.resolve()),
} as unknown as Parameters<typeof deploy>[0];

const context: nxDevKit.ExecutorContext = {
root: mockProjectRoot,
projectName: PROJECT,
target: {
executor: 'ngx-deploy-npm:deploy',
},
projectGraph: {},
} as nxDevKit.ExecutorContext;

return {
PROJECT,
context,
mockEngine,
};
};

it('should invoke the engine', async () => {
const { mockEngine, context } = setup();
const options: DeployExecutorOptions = {
distFolderPath: 'dist/libs/project',
access: 'public',
};

await deploy(mockEngine, context, options);

expect(mockEngine.run).toHaveBeenCalledWith(
path.join(context.root, options.distFolderPath),
options
);
});
});
14 changes: 14 additions & 0 deletions packages/ngx-deploy-npm/src/executors/deploy/actions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { ExecutorContext } from '@nx/devkit';

import { DeployExecutorOptions } from './schema';
import * as path from 'path';

export default async function deploy(
engine: {
run: (dir: string, options: DeployExecutorOptions) => Promise<void>;
},
context: ExecutorContext,
options: DeployExecutorOptions
) {
await engine.run(path.join(context.root, options.distFolderPath), options);
}
277 changes: 277 additions & 0 deletions packages/ngx-deploy-npm/src/executors/deploy/engine/engine.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,277 @@
import { DeployExecutorOptions } from '../schema';
import { npmAccess } from '../../../core';
import * as engine from './engine';
import * as spawn from '../utils/spawn-async';
import * as setPackage from '../utils/set-package-version';
import { mockProjectDist, mockProjectRoot } from '../../../__mocks__/mocks';
import * as fileUtils from '../../../utils';

jest.mock('../../../utils', () => {
return {
__esModule: true, // <----- this __esModule: true is important
...jest.requireActual('../../../utils'),
};
});

describe('engine', () => {
const defaultOption: Readonly<Omit<DeployExecutorOptions, 'distFolderPath'>> =
Object.freeze({
access: npmAccess.public,
});
const setup = ({
options = defaultOption,
rootProject = mockProjectRoot,
distFolderPath = mockProjectDist(),
spawnAsyncMock = () => Promise.resolve(),
}: {
rootProject?: string;
distFolderPath?: string;
spawnAsyncMock?: (
mainProgram: string,
programArgs?: string[]
) => Promise<void>;
options?: Omit<DeployExecutorOptions, 'distFolderPath'>;
}) => {
const fullOptions: DeployExecutorOptions = {
...options,
distFolderPath,
};
jest.spyOn(spawn, 'spawnAsync').mockImplementation(spawnAsyncMock);

return {
absoluteDistFolderPath: `${rootProject}/${distFolderPath}`,
options: fullOptions,
};
};

afterEach(() => {
jest.clearAllMocks();
});

it('should call NPM Publish with the right options', async () => {
const expectedOptionsArray = [
'--access',
npmAccess.restricted,
'--tag',
'next',
'--otp',
'someValue',
'--dry-run',
'true',
'--registry',
'http://localhost:4873',
];
const { absoluteDistFolderPath, options } = setup({
options: {
access: npmAccess.restricted,
tag: 'next',
otp: 'someValue',
registry: 'http://localhost:4873',
dryRun: true,
},
});

await engine.run(absoluteDistFolderPath, options);

expect(spawn.spawnAsync).toHaveBeenCalledWith('npm', [
'publish',
absoluteDistFolderPath,
...expectedOptionsArray,
]);
});

it('should indicate that an error occurred when there is an error publishing the package', async () => {
const { absoluteDistFolderPath, options } = setup({
spawnAsyncMock: () => Promise.reject(new Error('custom error')),
});

await expect(() =>
engine.run(absoluteDistFolderPath, options)
).rejects.toThrow();
});

describe('Package.json Feature', () => {
const pJsonSetup = ({
version = '1.0.1-next0',
setPackageReturnValue = Promise.resolve(),
...originalSetupOptions
}: {
version?: string;
setPackageReturnValue?: Promise<void>;
} & Parameters<typeof setup>[0]) => {
jest
.spyOn(setPackage, 'setPackageVersion')
.mockImplementation(() => setPackageReturnValue);

if (!originalSetupOptions.options) {
originalSetupOptions.options = { ...defaultOption };
}

originalSetupOptions.options.packageVersion = version;

return {
version,
...setup(originalSetupOptions),
};
};

it('should write the version of the sent on the package.json', async () => {
const { absoluteDistFolderPath, version, options } = pJsonSetup({});

await engine.run(absoluteDistFolderPath, options);

expect(setPackage.setPackageVersion).toHaveBeenCalledWith(
absoluteDistFolderPath,
version
);
});

it('should not write the version of the sent on the package.json if is on dry-run mode', async () => {
const { absoluteDistFolderPath, options } = pJsonSetup({
options: {
access: npmAccess.public,
dryRun: true,
},
});

await engine.run(absoluteDistFolderPath, options);

expect(setPackage.setPackageVersion).not.toHaveBeenCalled();
});
});

describe('Package Version Check Feature', () => {
const defaultMockPackageJson = {
name: '@test/package',
version: '1.0.0',
};

const versionCheckSetup = ({
mockPackageJson = defaultMockPackageJson,
npmViewResult = () => Promise.resolve(),
npmPublishResult = () => Promise.resolve(),
defaultSpawnMock = () => Promise.resolve(),
...originalSetupOptions
}: {
mockPackageJson?: { name: string; version: string };
npmViewResult?: () => Promise<void>;
npmPublishResult?: () => Promise<void>;
defaultSpawnMock?: () => Promise<void>;
} & Omit<Parameters<typeof setup>[0], 'spawnAsyncMock'>) => {
jest
.spyOn(fileUtils, 'readFileAsync')
.mockImplementation(() =>
Promise.resolve(JSON.stringify(mockPackageJson))
);

const spawnAsyncMock: Parameters<typeof setup>[0]['spawnAsyncMock'] = (
_: string,
args?: string[]
): Promise<void> => {
return args?.[0] === 'view'
? npmViewResult()
: args?.[0] === 'publish'
? npmPublishResult()
: defaultSpawnMock();
};

return {
mockPackageJson,
...setup({
...originalSetupOptions,
spawnAsyncMock,
}),
};
};

it('should skip publishing when package exists and checkExisting is warning', async () => {
const { absoluteDistFolderPath, options, mockPackageJson } =
versionCheckSetup({
options: {
...defaultOption,
checkExisting: 'warning',
},
npmViewResult: () => Promise.resolve(),
npmPublishResult: () => Promise.resolve(),
});

await engine.run(absoluteDistFolderPath, {
...options,
checkExisting: 'warning',
});

// Verify package check was performed
expect(spawn.spawnAsync).toHaveBeenCalledWith('npm', [
'view',
`${mockPackageJson.name}@${mockPackageJson.version}`,
'version',
]);

// Verify publish was not called
expect(spawn.spawnAsync).not.toHaveBeenCalledWith(
'npm',
expect.arrayContaining(['publish'])
);
});

it('should throw error when package exists and checkExisting is "error"', async () => {
const { absoluteDistFolderPath, options, mockPackageJson } =
versionCheckSetup({
options: {
...defaultOption,
checkExisting: 'error',
},
});

// Should throw specific error when package exists
await expect(() =>
engine.run(absoluteDistFolderPath, {
...options,
checkExisting: 'error',
})
).rejects.toThrow(
`Package ${mockPackageJson.name}@${mockPackageJson.version} already exists in registry.`
);

// Verify check was performed but publish was not attempted
expect(spawn.spawnAsync).toHaveBeenCalledWith('npm', [
'view',
`${mockPackageJson.name}@${mockPackageJson.version}`,
'version',
]);
expect(spawn.spawnAsync).not.toHaveBeenCalledWith(
'npm',
expect.arrayContaining(['publish'])
);
});

it('should proceed with publishing when package does not exist', async () => {
const { absoluteDistFolderPath, options, mockPackageJson } =
versionCheckSetup({
options: {
...defaultOption,
checkExisting: 'warning',
},
npmViewResult: () => Promise.reject({ code: 'E404' }),
});

await engine.run(absoluteDistFolderPath, {
...options,
checkExisting: 'warning',
});

expect(spawn.spawnAsync).toHaveBeenNthCalledWith(1, 'npm', [
'view',
`${mockPackageJson.name}@${mockPackageJson.version}`,
'version',
]);

expect(spawn.spawnAsync).toHaveBeenNthCalledWith(2, 'npm', [
'publish',
absoluteDistFolderPath,
'--access',
'public',
]);
});
});
});
149 changes: 149 additions & 0 deletions packages/ngx-deploy-npm/src/executors/deploy/engine/engine.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
import { logger } from '@nx/devkit';
import * as fileUtils from '../../../utils';
import * as path from 'path';

import { setPackageVersion, NpmPublishOptions, spawnAsync } from '../utils';
import { DeployExecutorOptions } from '../schema';

async function checkIfPackageExists(
packageName: string,
version: string,
npmOptions: NpmPublishOptions
): Promise<boolean> {
try {
const args = ['view', `${packageName}@${version}`, 'version'];
if (npmOptions.registry) {
args.push('--registry', npmOptions.registry);
}
await spawnAsync('npm', args);
return true;
} catch {
return false;
}
}

async function getPackageInfo(
distFolderPath: string
): Promise<{ name: string; version: string }> {
const packageContent = await fileUtils.readFileAsync(
path.join(distFolderPath, 'package.json'),
{ encoding: 'utf8' }
);
const packageJson = JSON.parse(packageContent);
return {
name: packageJson.name,
version: packageJson.version,
};
}

export async function run(
distFolderPath: string,
options: DeployExecutorOptions
) {
try {
if (options.dryRun) {
logger.info('Dry-run: The package is not going to be published');
}

/*
Modifying the dist when the user is dry-run mode,
thanks to the Nx Cache could lead to leading to publishing and unexpected package version
when the option is removed
*/
if (options.packageVersion && !options.dryRun) {
await setPackageVersion(distFolderPath, options.packageVersion);
}

const npmOptions = extractOnlyNPMOptions(options);

// Only check for existing package if explicitly enabled
if (
options.checkExisting &&
['error', 'warning'].includes(options.checkExisting)
) {
const packageInfo = await getPackageInfo(distFolderPath);
const exists = await checkIfPackageExists(
packageInfo.name,
packageInfo.version,
npmOptions
);

if (exists) {
if (options.checkExisting === 'error') {
const message = `Package ${packageInfo.name}@${
packageInfo.version
} already exists in registry${
options.registry ? ` ${options.registry}` : ''
}.`;
throw new Error(message);
} else {
logger.warn(
`Package ${packageInfo.name}@${packageInfo.version} already exists in registry. Skipping publish.`
);
return;
}
}
}

await spawnAsync('npm', [
'publish',
distFolderPath,
...getOptionsStringArr(npmOptions),
]);

if (options.dryRun) {
logger.info('The options are:');
logger.info(JSON.stringify(options, null, 1));
}

logger.info(
'🚀 Successfully published via ngx-deploy-npm! Have a nice day!'
);
} catch (error) {
logger.error('❌ An error occurred!');
throw error;
}
}

/**
* Extract only the options that the `npm publish` command can process
*
* @param param0 All the options sent to deploy
*/
function extractOnlyNPMOptions({
access,
tag,
otp,
dryRun,
registry,
}: DeployExecutorOptions): NpmPublishOptions {
return {
access,
tag,
otp,
dryRun,
registry,
};
}

function getOptionsStringArr(options: NpmPublishOptions): string[] {
return (
Object.keys(options)
// Get only options with value
.filter(optKey => !!(options as Record<string, unknown>)[optKey])
// to CMD option
.map(optKey => ({
cmdOptions: `--${toKebabCase(optKey)}`,
value: (options as Record<string, string | boolean | number>)[optKey],
}))
// push the command and the value to the array
.flatMap(cmdOptionValue => [
cmdOptionValue.cmdOptions,
cmdOptionValue.value.toString(),
])
);

function toKebabCase(str: string) {
return str.replace(/([a-z0-9]|(?=[A-Z]))([A-Z])/g, '$1-$2').toLowerCase();
}
}
22 changes: 22 additions & 0 deletions packages/ngx-deploy-npm/src/executors/deploy/executor.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { ExecutorContext, logger } from '@nx/devkit';

import deploy from './actions';
import * as engine from './engine/engine';
import { DeployExecutorOptions } from './schema';

export default async function runExecutor(
options: DeployExecutorOptions,
context: ExecutorContext
) {
try {
await deploy(engine, context, options);
} catch (e) {
logger.error(e);
logger.error('Error when trying to publish the library');
return { success: false };
}

return {
success: true,
};
}
34 changes: 34 additions & 0 deletions packages/ngx-deploy-npm/src/executors/deploy/schema.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
export interface DeployExecutorOptions {
/**
* The dist folder path. The path should be relative to the project's root
*/
distFolderPath: string;
/**
* The version that your package is going to be published. Ex: '1.3.5' '2.0.0-next.0'
*/
packageVersion?: string;
/**
* Registers the published package with the given tag, such that `npm install @` will install this version. By default, `npm publish` updates and `npm install` installs the `latest` tag. See `npm-dist-tag` for details about tags.
*/
tag?: string;
/**
* Tells the registry whether this package should be published as public or restricted. Only applies to scoped packages, which default to restricted. If you don’t have a paid account, you must publish with --access public to publish scoped packages.
*/
access: 'public' | 'restricted';
/**
* If you have two-factor authentication enabled in auth-and-writes mode then you can provide a code from your authenticator with this. If you don’t include this and you’re running from a TTY then you’ll be prompted.
*/
otp?: string | number;
/**
* Configure npm to use any compatible registry you like, and even run your own registry.
*/
registry?: string;
/**
* For testing: Run through without making any changes. Execute with --dry-run and nothing will happen.
*/
dryRun?: boolean;
/**
* Check if the package version already exists before publishing
*/
checkExisting?: 'error' | 'warning';
}
47 changes: 47 additions & 0 deletions packages/ngx-deploy-npm/src/executors/deploy/schema.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
{
"version": 2,
"outputCapture": "direct-nodejs",
"$schema": "http://json-schema.org/schema",
"title": "Deploy executor",
"description": "Publish your libraries to NPM with just one command",
"type": "object",
"properties": {
"distFolderPath": {
"type": "string",
"description": "The dist folder path. The path should be relative to the project's root"
},
"packageVersion": {
"type": "string",
"description": "The version that your package is going to be published. Ex: '1.3.5' '2.0.0-next.0'"
},
"tag": {
"type": "string",
"description": "Registers the published package with the given tag, such that `npm install @` will install this version. By default, `npm publish` updates and `npm install` installs the `latest` tag. See `npm-dist-tag` for details about tags."
},
"access": {
"type": "string",
"description": "Tells the registry whether this package should be published as public or restricted. Only applies to scoped packages, which default to restricted. If you don't have a paid account, you must publish with --access public to publish scoped packages.",
"enum": ["public", "restricted"],
"default": "public"
},
"otp": {
"type": ["string", "number"],
"description": "If you have two-factor authentication enabled in auth-and-writes mode then you can provide a code from your authenticator with this. If you don't include this and you're running from a TTY then you'll be prompted."
},
"registry": {
"type": "string",
"description": "Configure npm to use any compatible registry you like, and even run your own registry."
},
"dryRun": {
"type": "boolean",
"description": "For testing: Run through without making any changes. Execute with --dry-run and nothing will happen.",
"default": false
},
"checkExisting": {
"type": "string",
"description": "How to handle existing package versions: 'warning' to continue with a warning, 'error' to abort publishing",
"enum": ["warning", "error"]
}
},
"required": ["distFolderPath"]
}
3 changes: 3 additions & 0 deletions packages/ngx-deploy-npm/src/executors/deploy/utils/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export * from './interfaces';
export * from './set-package-version';
export * from './spawn-async';
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { DeployExecutorOptions } from '../schema';

export type NpmPublishOptions = Pick<
DeployExecutorOptions,
'access' | 'tag' | 'otp' | 'dryRun' | 'registry'
>;
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import * as fileUtils from '../../../utils';
import { setPackageVersion } from './set-package-version';

jest.mock('../../../utils', () => {
return {
__esModule: true, // <----- this __esModule: true is important
...jest.requireActual('../../../utils'),
};
});

describe('setPackageVersion', () => {
let myPackageJSON: Record<string, unknown>;
let expectedPackage: Record<string, unknown>;
let version: string;
let dir: string;

let valueWriten: Parameters<typeof fileUtils.writeFileAsync>[1];

// Spies
beforeEach(() => {
jest
.spyOn(fileUtils, 'readFileAsync')
.mockImplementation(() => Promise.resolve(JSON.stringify(myPackageJSON)));

jest.spyOn(fileUtils, 'writeFileAsync').mockImplementation((_, data) => {
valueWriten = data;
return Promise.resolve();
});
});

// Data
beforeEach(() => {
version = '1.0.1-next0';
dir = 'some/random/dir';

myPackageJSON = {
name: 'ngx-deploy-npm',
version: 'boilerPlate',
description: 'Publish your libraries to NPM with just one command',
main: 'index.js',
};

expectedPackage = {
...myPackageJSON,
version,
};
});

it('should write the version of the sent on the package.json', async () => {
await setPackageVersion(dir, version);

expect(valueWriten).toEqual(JSON.stringify(expectedPackage, null, 4));
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import * as fileUtils from '../../../utils';
import * as path from 'path';

export async function setPackageVersion(dir: string, packageVersion: string) {
const packageContent: string = await fileUtils.readFileAsync(
path.join(dir, 'package.json'),
{ encoding: 'utf8' }
);

const packageObj = JSON.parse(packageContent);

packageObj.version = packageVersion;

await fileUtils.writeFileAsync(
path.join(dir, 'package.json'),
JSON.stringify(packageObj, null, 4),
{ encoding: 'utf8' }
);
}
138 changes: 138 additions & 0 deletions packages/ngx-deploy-npm/src/executors/deploy/utils/spawn-async.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
import { logger } from '@nx/devkit';

import { spawnAsync } from './spawn-async';
import * as child_process from 'child_process';
import type { SpawnMock } from '../../../__mocks__/child_process';
jest.mock('child_process');
const mockedChildProcess: SpawnMock = child_process as unknown as SpawnMock;

describe('spawnAsync', () => {
setMockPlatform('linux');

afterAll(() => {
jest.clearAllMocks();
});

it('should complete the promise when the command finish', async () => {
const command = 'ls';

const promise = spawnAsync(command);
mockedChildProcess.listOfChildProcess[command].emit('close', 0);
const returnedValue = await promise;

expect(returnedValue).toBeUndefined();
});

it('should reject the promise when the command finish with error code', async () => {
const command = 'ls';
const errorCode = 1;

await expect(() => {
const promise = spawnAsync(command);
mockedChildProcess.listOfChildProcess[command].emit('close', errorCode);

return promise;
}).rejects.toEqual(errorCode);
});

it('should reject the promise when the command emits on the error event', async () => {
const command = 'ls';
const errorData = 'error-data';

await expect(() => {
const promise = spawnAsync(command);
mockedChildProcess.listOfChildProcess[command].emit('error', errorData);

return promise;
}).rejects.toEqual(errorData);
});

it("should log the data of the command's standard output (stdout)", async () => {
const command = 'ls';
const buffer = Buffer.from('buffer with data');

const promise = spawnAsync(command);
mockedChildProcess.listOfChildProcess[command].stdout.emit('data', buffer);
mockedChildProcess.listOfChildProcess[command].emit('close', 0);
await promise;

expect(logger.info).toHaveBeenCalledWith(buffer.toString());
});

it("should log the data of the command's standard error (stderr)", async () => {
const command = 'ls';
const buffer = Buffer.from('buffer with data');

const promise = spawnAsync(command);
mockedChildProcess.listOfChildProcess[command].stderr.emit('data', buffer);
mockedChildProcess.listOfChildProcess[command].emit('close', 0);
await promise;

expect(logger.info).toHaveBeenCalledWith(buffer.toString());
});

describe('Windows OS', () => {
setMockPlatform('win32', {
comspec: 'C:\\Windows\\system\\cmd.exe',
});

it('should complete the promise when the command finish', async () => {
const command = 'dir';

const promise = spawnAsync(command);
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
mockedChildProcess.listOfChildProcess[process.env.comspec!].emit(
'close',
0
);
const returnedValue = await promise;

expect(returnedValue).toBeUndefined();
});

it('should have called original spawn with the right attributes', async () => {
const command = 'dir';
const commandParams = ['/w'];

const promise = spawnAsync(command, commandParams);
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
mockedChildProcess.listOfChildProcess[process.env.comspec!].emit(
'close',
0
);
await promise;

expect(mockedChildProcess.spawn).toHaveBeenCalledWith(
process.env.comspec,
['/c', command, ...commandParams]
);
});
});
});

function setMockPlatform(
mockPlatform: typeof process.platform,
environmentVar?: Record<string, string>
) {
const originalEnv = process.env;
const originalPlatform = process.platform;

beforeEach(() => {
jest.resetModules();
process.env = {
...originalEnv,
...(environmentVar ? environmentVar : {}),
};

Object.defineProperty(process, 'platform', {
value: mockPlatform,
});
});

afterEach(() => {
process.env = originalEnv;
Object.defineProperty(process, 'platform', {
value: originalPlatform,
});
});
}
35 changes: 35 additions & 0 deletions packages/ngx-deploy-npm/src/executors/deploy/utils/spawn-async.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { logger } from '@nx/devkit';
import { spawn } from 'child_process';

export function spawnAsync(
mainProgram: string,
programArgs?: string[]
): Promise<void> {
return new Promise((resolve, reject) => {
let command = mainProgram;
let args = programArgs ?? [];

if (process.platform === 'win32') {
command = process.env.comspec as string;
args = ['/c', mainProgram, ...args];
}

const childProcess = spawn(command, args);

childProcess.stdout.on('data', data => {
logger.info(data.toString());
});
childProcess.stderr.on('data', data => {
logger.info(data.toString());
});

childProcess.on('close', code => {
if (code === 0) {
resolve();
} else {
reject(code);
}
});
childProcess.on('error', reject);
});
}
283 changes: 283 additions & 0 deletions packages/ngx-deploy-npm/src/generators/install/generator.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,283 @@
import { createTreeWithEmptyWorkspace } from '@nx/devkit/testing';
import {
addProjectConfiguration,
ProjectConfiguration,
getProjects,
TargetConfiguration,
} from '@nx/devkit';

import generator from './generator';
import { InstallGeneratorOptions } from './schema';
import { DeployExecutorOptions } from '../../executors/deploy/schema';
import { npmAccess } from '../../core';
import * as mocks from '../../__mocks__/mocks';
import * as utils from '../../utils';

jest.mock('../../utils', () => {
return {
__esModule: true, // <----- this __esModule: true is important
...jest.requireActual('../../utils'),
};
});

describe('install generator', () => {
enum PUBLISHABLE_LIBS {
lib1 = 'lib1',
lib2 = 'lib2',
withoutBuildLib = 'withoutBuildLib',
}
enum NON_PUBLISHABLE_LIBS {
app = 'app',
nonPublishable = 'nonPublishable',
nonPublishable2 = 'nonPublishable2',
}

const allKindsOfProjects: Record<
PUBLISHABLE_LIBS | NON_PUBLISHABLE_LIBS,
ProjectConfiguration
> = {
lib1: mocks.getLib('lib1'),
lib2: mocks.getLib('lib2'),
withoutBuildLib: mocks.getLibWithoutBuildTarget('withoutBuildLib'),
app: mocks.getApplication('app'),
nonPublishable: mocks.getLibWithoutBuildTarget('nonPublishable'),
nonPublishable2: mocks.getLibWithoutBuildTarget('nonPublishable2'),
};

const setup = ({
workspaceProjects = allKindsOfProjects,
workspacePublishableLibs = Object.keys(PUBLISHABLE_LIBS)
.filter(v => isNaN(Number(v)))
.reduce((acc, key) => {
acc[key] = true;

return acc;
}, {} as Record<string, true>),
}: {
workspaceProjects?: Record<string, ProjectConfiguration>;
workspacePublishableLibs?: Record<string, true>;
}) => {
const workspaceConfig = new Map();
const appTree = createTreeWithEmptyWorkspace();

Object.entries(workspaceProjects).forEach(([key, project]) => {
workspaceConfig.set(key, project);
});

jest
.spyOn(utils, 'isProjectAPublishableLib')
.mockImplementation(project => {
const isAPublishableLib = project.name
? !!workspacePublishableLibs[project.name]
: false;

return Promise.resolve(isAPublishableLib);
});

// Create workspace
Array.from(workspaceConfig.entries()).forEach(([key, projectConfig]) =>
addProjectConfiguration(appTree, key, projectConfig)
);

return { appTree };
};

const buildMockDistPath = (projectName: string) => {
return `dist/libs/${projectName}`;
};

const buildExpectedDeployTarget = (
projectName: string,
isBuildable = true
): TargetConfiguration<DeployExecutorOptions> => {
const options: TargetConfiguration<DeployExecutorOptions> = {
executor: 'ngx-deploy-npm:deploy',
options: {
access: npmAccess.public,
distFolderPath: buildMockDistPath(projectName),
},
};

if (isBuildable) {
options.dependsOn = ['build'];
}

return options;
};

afterEach(jest.restoreAllMocks);
it('should create the target with the right structure for a buildable lib', async () => {
const projectName = 'buildableLib';
const { appTree } = setup({
workspaceProjects: {
[projectName]: mocks.getLib(projectName),
},
workspacePublishableLibs: {
[projectName]: true,
},
});

await generator(appTree, {
project: projectName,
distFolderPath: buildMockDistPath(projectName),
access: npmAccess.public,
});

const allProjects = getProjects(appTree);
const config = allProjects.get(projectName);
const targetDeploy = config?.targets?.deploy;

expect(targetDeploy).toStrictEqual(
buildExpectedDeployTarget(projectName, true)
);
});

it('should create the target with the right structure for a non-buildable lib', async () => {
const projectName = 'nonBuildableLib';
const { appTree } = setup({
workspaceProjects: {
[projectName]: mocks.getLibWithoutBuildTarget(projectName),
},
workspacePublishableLibs: {
[projectName]: true,
},
});

await generator(appTree, {
project: projectName,
distFolderPath: buildMockDistPath(projectName),
access: npmAccess.public,
});

const allProjects = getProjects(appTree);
const config = allProjects.get(projectName);
const targetDeploy = config?.targets?.deploy;

expect(targetDeploy).toStrictEqual(
buildExpectedDeployTarget(projectName, false)
);
});

it('should add the target only to the specified project', async () => {
const { appTree } = setup({});

await generator(appTree, {
project: PUBLISHABLE_LIBS.lib1,
distFolderPath: buildMockDistPath(PUBLISHABLE_LIBS.lib1),
access: npmAccess.public,
});

const allProjects = getProjects(appTree);
const config = allProjects.get(PUBLISHABLE_LIBS.lib2);
const targetDeploy = config?.targets?.deploy;

expect(targetDeploy).toStrictEqual(undefined);
});

it('should create the target with the right structure for a lib without targets', async () => {
const projectName = 'targetLess';
const { appTree } = setup({
workspaceProjects: {
[projectName]: mocks.getTargetlessLib(projectName),
},
workspacePublishableLibs: { [projectName]: true },
});

await generator(appTree, {
project: projectName,
distFolderPath: buildMockDistPath(projectName),
access: npmAccess.public,
});

const allProjects = getProjects(appTree);
const project = allProjects.get(projectName);
const setOptions: InstallGeneratorOptions =
project?.targets?.deploy.options;

expect(setOptions.access).toEqual(npmAccess.public);
});

describe('--access', () => {
const setupAccess = (access: npmAccess) => {
const projectName = 'lib1';
const rawOptions: InstallGeneratorOptions = {
project: projectName,
distFolderPath: buildMockDistPath(projectName),
access,
};

const { appTree } = setup({
workspaceProjects: {
[projectName]: mocks.getLib(projectName),
},
workspacePublishableLibs: { [projectName]: true },
});

return { appTree, rawOptions, projectName };
};

it('should set the `access` option as `public` when is set to `public` on rawoption', async () => {
const { appTree, rawOptions, projectName } = setupAccess(
npmAccess.public
);

// install
await generator(appTree, rawOptions);

const allProjects = getProjects(appTree);
const project = allProjects.get(projectName);
const targetOptions: InstallGeneratorOptions =
project?.targets?.deploy.options;

expect(targetOptions.access).toEqual(npmAccess.public);
});

it('should set the `access` option as `restricted` when is set to `restricted` on rawoption', async () => {
const { appTree, rawOptions, projectName } = setupAccess(
npmAccess.restricted
);

// install
await generator(appTree, rawOptions);

const allProjects = getProjects(appTree);
const project = allProjects.get(projectName);
const targetOptions: InstallGeneratorOptions =
project?.targets?.deploy.options;

expect(targetOptions.access).toEqual(npmAccess.restricted);
});
});

describe('error handling', () => {
it('should throw an error if the project is not a publishable library', async () => {
const project = NON_PUBLISHABLE_LIBS.app;
const rawOptions: InstallGeneratorOptions = {
project,
distFolderPath: buildMockDistPath(project),
access: npmAccess.public,
};
const { appTree } = setup({});

await expect(generator(appTree, rawOptions)).rejects.toThrow(
new Error(`The project ${project} is not a publishable library`)
);
});

it('should throw an error if invalid project is pass on --project', async () => {
const invalidProjects = 'i-dont-exists';
const rawOptions: InstallGeneratorOptions = {
project: invalidProjects,
distFolderPath: buildMockDistPath(invalidProjects),
access: npmAccess.public,
};
const { appTree } = setup({});

await expect(generator(appTree, rawOptions)).rejects.toThrow(
new Error(
`The project ${invalidProjects} doesn't exist on your workspace`
)
);
});
});
});
51 changes: 51 additions & 0 deletions packages/ngx-deploy-npm/src/generators/install/generator.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import {
getProjects,
formatFiles,
updateProjectConfiguration,
} from '@nx/devkit';
import type { Tree } from '@nx/devkit';

import type { InstallGeneratorOptions } from './schema';
import { DeployExecutorOptions } from '../../executors/deploy/schema';
import { isProjectAPublishableLib } from '../../utils';

export default async function install(
tree: Tree,
rawOptions: InstallGeneratorOptions
) {
const options = rawOptions;

const selectedLib = getProjects(tree).get(options.project);

if (selectedLib === undefined) {
throw new Error(
`The project ${options.project} doesn't exist on your workspace`
);
}

if ((await isProjectAPublishableLib(selectedLib)) === false) {
throw new Error(
`The project ${options.project} is not a publishable library`
);
}

const executorOptions: DeployExecutorOptions = {
distFolderPath: options.distFolderPath,
access: options.access,
};

// Create targets in case that they doesn't already exists
if (!selectedLib.targets) {
selectedLib.targets = {};
}

selectedLib.targets.deploy = {
executor: 'ngx-deploy-npm:deploy',
options: executorOptions,
...(selectedLib.targets.build ? { dependsOn: ['build'] } : {}),
};

updateProjectConfiguration(tree, options.project, selectedLib);

await formatFiles(tree);
}
14 changes: 14 additions & 0 deletions packages/ngx-deploy-npm/src/generators/install/schema.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
export interface InstallGeneratorOptions {
/**
* The dist folder path. The path should be relative to the project's root
*/
distFolderPath: string;
/**
* Which library should configure
*/
project: string;
/**
* Tells the registry whether this package should be published as public or restricted. Only applies to scoped packages, which default to restricted. If you don’t have a paid account, you must publish with --access public to publish scoped packages.
*/
access: 'public' | 'restricted';
}
25 changes: 25 additions & 0 deletions packages/ngx-deploy-npm/src/generators/install/schema.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
{
"$schema": "http://json-schema.org/schema",
"$id": "Install",
"title": "",
"type": "object",
"properties": {
"project": {
"description": "Which library should configure. This will add the target deploy to that project. It must be a publishable library.",
"type": "string",
"x-prompt": "Which library should configure?"
},
"distFolderPath": {
"type": "string",
"description": "The dist folder path. The path should be relative to the project's root",
"x-prompt": "Which is the dist folder path?"
},
"access": {
"type": "string",
"description": "Tells the registry whether this package should be published as public or restricted. Only applies to scoped packages, which default to restricted. If you don't have a npm paid account, you must publish with --access public to publish scoped packages.",
"enum": ["public", "restricted"],
"default": "public"
}
},
"required": ["project", "distFolderPath"]
}
3 changes: 3 additions & 0 deletions packages/ngx-deploy-npm/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export type { InstallGeneratorOptions } from './generators/install/schema';
export type { DeployExecutorOptions } from './executors/deploy/schema';
export { npmAccess } from './core/npm-access.enum';

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
/* eslint-disable @typescript-eslint/no-unused-vars */
/**
Remove the old `configuration` option and:
- create a new target to build the library with the right configuration
- create the `dependsOn` option on the deploy target
- Remove deprecated options `buildTarget` and `noBuild`
*/
import {
ProjectConfiguration,
TargetConfiguration,
Tree,
formatFiles,
getProjects,
updateProjectConfiguration,
} from '@nx/devkit';
import { DeployExecutorOptions } from '../../executors/deploy/schema';

export type RemovedDeployExecutorOptions = {
/**
* A named build target, as specified in the `configurations`. Each named target is accompanied by a configuration of option defaults for that target. This is equivalent to calling the command `nx build --configuration=XXX`.
*/
buildTarget?: string;
/**
* Skip build process during deployment.
* Default: false
*/
noBuild?: boolean;
};

export type DeprecatedDeployExecutorOptions = DeployExecutorOptions &
RemovedDeployExecutorOptions;

export default async function update(host: Tree) {
dependsOnMigration(host);
removeDeprecatedOptionsMigration(host);

await formatFiles(host);
}

function dependsOnMigration(host: Tree) {
// Get projects that require creating of dependsOn
const projectToCreateDependsOn = Array.from(getProjects(host))
// remove projects that doesn't have the our executor or already have the option distFolderPath
.filter(([_, project]) => {
const projectTargets: TargetConfiguration<DeprecatedDeployExecutorOptions>[] =
Object.values(project.targets ?? {});

return projectTargets.some(targetRequiresMigrationCreationDependsOn);
});

// Create the `dependencyOn` the deploy target
projectToCreateDependsOn.forEach(([projectKey, project]) => {
createDependsOn(projectKey, project);
updateProjectConfiguration(host, projectKey, project);
});

function targetRequiresMigrationCreationDependsOn(
target: TargetConfiguration<DeprecatedDeployExecutorOptions>
) {
return (
target.executor === 'ngx-deploy-npm:deploy' && // has the right executor
target.options?.noBuild !== true // the target will build the library
);
}

function createDependsOn(projectKey: string, project: ProjectConfiguration) {
const deployExecutors: [
string,
TargetConfiguration<DeprecatedDeployExecutorOptions>
][] = Object.entries(project.targets ?? {}).filter(
([targetName, targetConfig]) =>
targetRequiresMigrationCreationDependsOn(targetConfig)
);

deployExecutors.forEach(([targetName, targetConfig]) => {
// store the old buildTarget value
const buildTarget = targetConfig.options?.buildTarget;

// Create targets in case that they doesn't already exists
if (!project.targets) {
project.targets = {};
}

// If our executor was building the library, it needs to depend on the build target
let newDependsOn = 'build';
if (typeof buildTarget === 'string') {
const newPreDeployTargetName = `pre-${targetName}-build-${buildTarget}`;
newDependsOn = newPreDeployTargetName;

project.targets[newPreDeployTargetName] = {
executor: 'nx:run-commands',
options: {
command: `nx run ${projectKey}:build:${buildTarget}`,
},
};
}

const dependsOn = project.targets[targetName].dependsOn;
if (dependsOn === undefined) {
project.targets[targetName].dependsOn = [];
}

project.targets[targetName].dependsOn?.push(newDependsOn);
});
}
}

async function removeDeprecatedOptionsMigration(host: Tree) {
// Get projects that require removing of buildTarget and noBuild
const projectToMigrate = Array.from(getProjects(host))
// remove projects that doesn't have the our executor or already have the option distFolderPath
.filter(([_, project]) => {
const projectTargets: TargetConfiguration<DeprecatedDeployExecutorOptions>[] =
Object.values(project.targets ?? {});

return projectTargets.some(targetRequiresMigration);
});

// Remove Deprecated options the `dependencyOn` the deploy target
projectToMigrate.forEach(([projectKey, project]) => {
removeBuildTargetAndNoBuildOptions(project);
updateProjectConfiguration(host, projectKey, project);
});

function targetRequiresMigration(
target: TargetConfiguration<DeprecatedDeployExecutorOptions>
) {
return (
target.executor === 'ngx-deploy-npm:deploy' && // has the right executor
(target.options?.noBuild !== undefined || // the target will build the library
target?.options?.buildTarget !== undefined) // the target has the buildTarget option
);
}

function removeBuildTargetAndNoBuildOptions(project: ProjectConfiguration) {
const deployExecutors: [
string,
TargetConfiguration<DeprecatedDeployExecutorOptions>
][] = Object.entries(project.targets ?? {}).filter(([_, targetConfig]) =>
targetRequiresMigration(targetConfig)
);

deployExecutors.forEach(([_, targetConfig]) => {
// Remove option build target
delete targetConfig.options?.buildTarget;
delete targetConfig.options?.noBuild;
});
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
import { createTreeWithEmptyWorkspace } from '@nx/devkit/testing';
import {
ProjectConfiguration,
TargetConfiguration,
Tree,
addProjectConfiguration,
getProjects,
} from '@nx/devkit';
import * as mocks from '../../__mocks__/mocks';

import update, {
DeprecatedDeployExecutorOptions,
} from './write-dist-folder-path-on-deploy-target-options';

describe('write-dist-folder-path-on-deploy-target-options migration', () => {
const setUp = () => {
const addTargets = (
project: ProjectConfiguration,
addDistFolderPathOption = true
) => {
if (!project.targets) {
project.targets = {};
}

const deployTarget: TargetConfiguration<DeprecatedDeployExecutorOptions> =
{
executor: 'ngx-deploy-npm:deploy',
options: {
distFolderPath: addDistFolderPathOption
? `dist/libs/${project.name}`
: undefined,
access: 'public',
},
};

project.targets.deploy = deployTarget;
project.targets.publish = deployTarget;

return project;
};

const nonMigratedProjects: Record<string, ProjectConfiguration> = {
WITH_distFolderPathOption: addTargets(
mocks.getLib('WITH_distFolderPathOption')
),
WITHOUT_distFolderPathOption1: addTargets(
mocks.getLib('WITHOUT_distFolderPathOption1'),
false
),
WITHOUT_distFolderPathOption2: addTargets(
mocks.getLib('WITHOUT_distFolderPathOption2'),
false
),

app: mocks.getApplication('app'),
nonPublishable: mocks.getLibWithoutBuildTarget('nonPublishable'),
};

const tree: Tree = createTreeWithEmptyWorkspace();

Object.entries(nonMigratedProjects).forEach(([key, projectConfig]) =>
addProjectConfiguration(tree, key, projectConfig)
);

return { tree, nonMigratedProjects };
};

it('should set up the distFolderPath option on the right projects', async () => {
const { tree } = setUp();

await update(tree);

const allProjects = getProjects(tree);
const WITHOUT_distFolderPathOption1 = allProjects.get(
'WITHOUT_distFolderPathOption1'
);
const WITHOUT_distFolderPathOption2 = allProjects.get(
'WITHOUT_distFolderPathOption2'
);

expect(
WITHOUT_distFolderPathOption1?.targets?.deploy.options.distFolderPath
).toBeTruthy();
expect(
WITHOUT_distFolderPathOption1?.targets?.publish.options.distFolderPath
).toBeTruthy();
expect(
WITHOUT_distFolderPathOption2?.targets?.deploy.options.distFolderPath
).toBeTruthy();
expect(
WITHOUT_distFolderPathOption2?.targets?.publish.options.distFolderPath
).toBeTruthy();
});

it('should set up the distFolderPath option with the right value', async () => {
const { tree } = setUp();

await update(tree);

const allProjects = getProjects(tree);
const WITHOUT_distFolderPathOption1 = allProjects.get(
'WITHOUT_distFolderPathOption1'
);

expect(
WITHOUT_distFolderPathOption1?.targets?.deploy.options.distFolderPath
).toStrictEqual(`dist/libs/${WITHOUT_distFolderPathOption1?.name ?? ''}`);
expect(
WITHOUT_distFolderPathOption1?.targets?.publish.options.distFolderPath
).toStrictEqual(`dist/libs/${WITHOUT_distFolderPathOption1?.name ?? ''}`);
});

it('should not touch other projects', async () => {
const { tree } = setUp();
const getNonMigratedProjects = (tree: Tree) => {
const allProjects = getProjects(tree);
return {
projectBefore: allProjects.get('projectBefore'),
WITH_distFolderPathOption: allProjects.get('WITH_distFolderPathOption'),
app: allProjects.get('app'),
nonPublishable: allProjects.get('nonPublishable'),
};
};
const nonMigratedProjectsBefore = getNonMigratedProjects(tree);

await update(tree);

const nonMigratedProjectsAfter = getNonMigratedProjects(tree);

expect(nonMigratedProjectsBefore).toStrictEqual(nonMigratedProjectsAfter);
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import {
TargetConfiguration,
Tree,
formatFiles,
getProjects,
updateProjectConfiguration,
logger,
} from '@nx/devkit';
import { DeployExecutorOptions } from '../../executors/deploy/schema';

export type DeprecatedDeployExecutorOptions = Omit<
DeployExecutorOptions,
'distFolderPath'
> & { distFolderPath?: string };

const targetNeedsMigration = (
target: TargetConfiguration<DeprecatedDeployExecutorOptions>
) =>
target.executor === 'ngx-deploy-npm:deploy' &&
target.options?.distFolderPath === undefined;

export default async function update(host: Tree) {
const projectToMigrate = Array.from(getProjects(host))
// remove projects that doesn't have the our executor or already have the option distFolderPath
.filter(([, project]) => {
const projectTargets: TargetConfiguration<DeprecatedDeployExecutorOptions>[] =
Object.values(project.targets ?? {});
return projectTargets.some(targetNeedsMigration);
});

projectToMigrate.forEach(([projectKey, project]) => {
if (project.name === undefined) {
logger.warn(
`Project ${projectKey} doesn't have a name\nIt was skipped, you will need to create the option distFolderPath manually`
);
return;
}

// Create the distFolderPath option
Object.values(project.targets ?? {})
.filter(targetNeedsMigration)
.forEach(deployTarget => {
deployTarget.options.distFolderPath = `dist/${project.root}`;
});
updateProjectConfiguration(host, project.name, project);
});

await formatFiles(host);
}
45 changes: 45 additions & 0 deletions packages/ngx-deploy-npm/src/utils/file-utils.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import * as fs from 'fs';
import * as path from 'path';
import { fileExists } from './file-utils';

describe('File Utils', () => {
describe('FileExists', () => {
let fakeFilePath: string;

const setUp = ({ fileShouldExists }: { fileShouldExists: boolean }) => {
const accessSpy = jest
.spyOn(fs, 'access')
.mockImplementation((_: fs.PathLike, callback: fs.NoParamCallback) => {
if (fileShouldExists) {
callback(null);
} else {
callback(new Error('File not found'));
}
});

return {
accessSpy,
};
};

beforeEach(() => {
fakeFilePath = path.join('random', 'path', 'file', 'package.json');
});

it('should indicate that the file exists', async () => {
setUp({ fileShouldExists: true });

const response = await fileExists(fakeFilePath);

expect(response).toBe(true);
});

it('should indicate that the file exists', async () => {
setUp({ fileShouldExists: false });

const response = await fileExists(fakeFilePath);

expect(response).toBe(false);
});
});
});
15 changes: 15 additions & 0 deletions packages/ngx-deploy-npm/src/utils/file-utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { promisify } from 'util';
import { readFile, writeFile, access } from 'fs';

export const readFileAsync = promisify(readFile);

export const writeFileAsync = promisify(writeFile);

export async function fileExists(filePath: string) {
try {
await promisify(access)(filePath);
return true;
} catch {
return false;
}
}
2 changes: 2 additions & 0 deletions packages/ngx-deploy-npm/src/utils/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from './file-utils';
export * from './is-a-lib';
85 changes: 85 additions & 0 deletions packages/ngx-deploy-npm/src/utils/is-a-lib.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import { isProjectAPublishableLib } from './is-a-lib';
import * as fileUtils from './file-utils';
import { ProjectConfiguration } from '@nx/devkit';
import * as mocks from '../__mocks__/mocks';
import * as path from 'path';

describe('is-a-lib', () => {
const setUp = () => {
const projects: Record<
'publishableLibrary' | 'nonPublishableLibrary' | 'app',
ProjectConfiguration
> = {
publishableLibrary: mocks.getLib('pub-lib'),
nonPublishableLibrary: mocks.getLib('non-pub-lib'),
app: mocks.getApplication('app'),
};

return { projects };
};

describe('isProjectAPublishableLib', () => {
const setUpIsProjectAPublishableLib = ({
shouldPackageJsonExists,
}: {
shouldPackageJsonExists: boolean;
}) => {
const { projects } = setUp();

const fileExistsMock = jest
.spyOn(fileUtils, 'fileExists')
.mockImplementation(() => Promise.resolve(shouldPackageJsonExists));

return { fileExistsMock, projects };
};

afterEach(jest.restoreAllMocks);

it('should indicate that the project is a publishable library', async () => {
const { projects } = setUpIsProjectAPublishableLib({
shouldPackageJsonExists: true,
});

const response = await isProjectAPublishableLib(
projects.publishableLibrary
);

expect(response).toBe(true);
});

it('should indicate that the project is a non publishable library (app)', async () => {
const { projects } = setUpIsProjectAPublishableLib({
shouldPackageJsonExists: true,
});

const response = await isProjectAPublishableLib(projects.app);

expect(response).toBe(false);
});

it('should indicate that the project is a non publishable library (non publishable library)', async () => {
const { projects } = setUpIsProjectAPublishableLib({
shouldPackageJsonExists: false,
});

const response = await isProjectAPublishableLib(
projects.nonPublishableLibrary
);

expect(response).toBe(false);
});

it('should look for package.json file', async () => {
const { fileExistsMock, projects } = setUpIsProjectAPublishableLib({
shouldPackageJsonExists: false,
});
const project = projects.nonPublishableLibrary;

await isProjectAPublishableLib(project);

expect(fileExistsMock.mock.calls[0][0]).toEqual(
path.join(project.root, 'package.json')
);
});
});
});
15 changes: 15 additions & 0 deletions packages/ngx-deploy-npm/src/utils/is-a-lib.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { ProjectConfiguration } from '@nx/devkit';
import { fileExists } from './file-utils';
import * as path from 'path';

export const isProjectAPublishableLib = async (
project: ProjectConfiguration
): Promise<boolean> => {
return (
project.projectType === 'library' && (await hasProjectJsonFile(project))
);
};

function hasProjectJsonFile(project: ProjectConfiguration): Promise<boolean> {
return fileExists(path.join(project.root, 'package.json'));
}
13 changes: 13 additions & 0 deletions packages/ngx-deploy-npm/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"extends": "../../tsconfig.base.json",
"files": [],
"include": [],
"references": [
{
"path": "./tsconfig.lib.json"
},
{
"path": "./tsconfig.spec.json"
}
]
}
11 changes: 11 additions & 0 deletions packages/ngx-deploy-npm/tsconfig.lib.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"module": "commonjs",
"outDir": "../../dist/out-tsc",
"declaration": true,
"types": ["node"]
},
"include": ["**/*.ts"],
"exclude": ["**/*.spec.ts", "**/__mocks__/*"]
}
16 changes: 16 additions & 0 deletions packages/ngx-deploy-npm/tsconfig.spec.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"outDir": "../../dist/out-tsc",
"module": "commonjs",
"types": ["jest", "node"]
},
"include": [
"**/*.spec.ts",
"**/*.spec.tsx",
"**/*.spec.js",
"**/*.spec.jsx",
"**/*.d.ts",
"src/__mocks__/**/*.ts"
]
}
14 changes: 14 additions & 0 deletions project.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"name": "bikecoders",
"$schema": "node_modules/nx/schemas/project-schema.json",
"targets": {
"local-registry": {
"executor": "@nx/js:verdaccio",
"options": {
"port": 4873,
"config": ".verdaccio/config.yml",
"storage": "tmp/local-registry/storage"
}
}
}
}
25 changes: 25 additions & 0 deletions renovate.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
{
"$schema": "http://json.schemastore.org/renovate",
"extends": ["config:base", ":semanticCommitScopeDisabled"],
"baseBranches": ["main"],
"rebaseWhen": "behind-base-branch",
"lockFileMaintenance": {
"enabled": true,
"automerge": true,
"automergeType": "pr",
"commitMessageAction": "📦 Lock file maintenance"
},
"packageRules": [
{
"packagePatterns": ["*"],
"semanticCommitType": "build"
},
{
"matchPackagePatterns": ["^@nx"],
"schedule": ["before 3am"]
}
],
"rangeStrategy": "update-lockfile",
"commitMessageAction": "📦",
"schedule": ["before 3am on the first day of the month"]
}
32 changes: 32 additions & 0 deletions sonar-project.properties
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
sonar.organization=bikecoders
sonar.projectKey=bikecoders_ngx-deploy-npm
sonar.projectName=ngx-deploy-npm
sonar.sourceEncoding=UTF-8
sonar.project.monorepo.enabled=true

# List of module indedtifiers
sonar.modules=ngx-deploy-npm

# Common settings
# Files to exclude
sonar.exclusions=**/node_modules/**
# How to find the test
sonar.test.inclusions=**/*.spec.ts,**/__mocks__/**

# ------- ngx-deploy-npm -------
ngx-deploy-npm.sonar.projectBaseDir=packages/ngx-deploy-npm
ngx-deploy-npm.sonar.sources=src
ngx-deploy-npm.sonar.tests=src
ngx-deploy-npm.sonar.typescript.tsconfigPath=tsconfig.lib.json

# Linter Report
# We standardize all the Sonar Analysis using dockerization to be able to provide absolute paths
ngx-deploy-npm.sonar.eslint.reportPaths=../../reports/ngx-deploy-npm/lint-report.info

# The coverage report
# We standardize all the Sonar Analysis using dockerization to be able to provide absolute paths
ngx-deploy-npm.sonar.javascript.lcov.reportPaths=../../coverage/packages/ngx-deploy-npm/lcov.info
# ------- ngx-deploy-npm off -------

# For more information visit
# https://docs.sonarqube.org/display/SONAR/Analysis+Parameters
64 changes: 0 additions & 64 deletions src/README.md

This file was deleted.

10 changes: 0 additions & 10 deletions src/builders.json

This file was deleted.

9 changes: 0 additions & 9 deletions src/collection.json

This file was deleted.

86 changes: 0 additions & 86 deletions src/deploy/actions.spec.ts

This file was deleted.

37 changes: 0 additions & 37 deletions src/deploy/actions.ts

This file was deleted.

65 changes: 0 additions & 65 deletions src/deploy/builder.ts

This file was deleted.

19 changes: 0 additions & 19 deletions src/deploy/schema.json

This file was deleted.

10 changes: 0 additions & 10 deletions src/engine/engine.spec.ts

This file was deleted.

25 changes: 0 additions & 25 deletions src/engine/engine.ts

This file was deleted.

1 change: 0 additions & 1 deletion src/index.ts

This file was deleted.

254 changes: 0 additions & 254 deletions src/ng-add.spec.ts

This file was deleted.

88 changes: 0 additions & 88 deletions src/ng-add.ts

This file was deleted.

5,727 changes: 0 additions & 5,727 deletions src/package-lock.json

This file was deleted.

72 changes: 0 additions & 72 deletions src/package.json

This file was deleted.

3 changes: 0 additions & 3 deletions src/public_api.ts

This file was deleted.

33 changes: 0 additions & 33 deletions src/tsconfig.json

This file was deleted.

38 changes: 38 additions & 0 deletions tools/docker/sonarqube/change_password.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
change_password () {
response=$(curl \
--write-out '%{http_code}' \
--silent \
--output /dev/null \
-u admin:admin \
-X POST \
"http://host.docker.internal:9000/api/users/change_password?login=admin&previousPassword=admin&password=$SONAR_PASSWORD")

if [ "$response" == "204" ]
then
echo "Server Responded with $response"
echo "Password changed successfully to $SONAR_PASSWORD"
true
elif [ "$response" == "401" ]
then
echo "Server Responded with $response"
echo "Password already changed. Is $SONAR_PASSWORD"
true
else
false
fi
}

try_to_change_pass () {
max_retry=20
counter=0

until change_password
do
sleep 4
[[ $counter -eq $max_retry ]] && echo "Failed trying to reach SonarQube server" && exit 1
echo -e "\nSonarQube server not accesible. Try #$counter"
let "counter+=1"
done
}

try_to_change_pass
2 changes: 2 additions & 0 deletions tools/docker/sonarqube/env_vars
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
SONAR_HOST_URL=http://host.docker.internal:9000
SONAR_PASSWORD=12345
5 changes: 5 additions & 0 deletions tools/scripts/scripts.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
declare global {
function stopLocalRegistry(): void;
}

export {};
23 changes: 23 additions & 0 deletions tools/scripts/start-local-registry.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/**
* This script starts a local registry for e2e testing purposes.
* It is meant to be called in jest's globalSetup.
*/
import { startLocalRegistry } from '@nx/js/plugins/jest/local-registry';
import { execSync } from 'child_process';

export default async () => {
// local registry target to run
const localRegistryTarget = 'bikecoders:local-registry';
// storage folder for the local registry
const storage = './tmp/local-registry/storage';

globalThis.stopLocalRegistry = await startLocalRegistry({
localRegistryTarget,
storage,
verbose: false,
});

execSync(
'npx nx deploy:without-build ngx-deploy-npm --registry=http://localhost:4873 --packageVersion=0.0.0 --tag e2e'
);
};
10 changes: 10 additions & 0 deletions tools/scripts/stop-local-registry.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
/**
* This script stops the local registry for e2e testing purposes.
* It is meant to be called in jest's globalTeardown.
*/

export default () => {
if (globalThis.stopLocalRegistry) {
globalThis.stopLocalRegistry();
}
};
24 changes: 24 additions & 0 deletions tools/sonarqube-linter-reporter.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
const path = require('path');

module.exports = function (results, context) {
const issues = results.filter(
({ errorCount, fatalErrorCount, warningCount }) =>
errorCount + fatalErrorCount + warningCount > 0
);

issues.forEach(
result =>
(result.filePath = result.filePath.replace(
/**
* If a new package comes in, this needs to be rethinked.
*
* Probably we would need a custom formater for each package
* or findind a regular expresion that will cover all the possible packages' path,
*/
path.join(context.cwd, '/', 'packages', 'ngx-deploy-npm', '/'),
'./'
))
);

return JSON.stringify(issues, null, 2);
};
24 changes: 24 additions & 0 deletions tsconfig.base.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
{
"compileOnSave": false,
"compilerOptions": {
"rootDir": ".",
"sourceMap": true,
"declaration": false,
"moduleResolution": "node",
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"importHelpers": true,
"target": "es2015",
"module": "esnext",
"lib": ["es2017", "dom"],
"skipLibCheck": true,
"skipDefaultLibCheck": true,
"baseUrl": ".",
"paths": {
"bikecoders/ngx-deploy-npm": ["packages/ngx-deploy-npm/src/index.ts"]
},
"strict": true,
"verbatimModuleSyntax": false
},
"exclude": ["node_modules", "tmp"]
}