antelope-cli 1.2.1 → 1.2.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -18,9 +18,19 @@ this role owns cloning, branching, committing, pushing and PR creation.
18
18
 
19
19
  - `git`, Node.js (engines `^16.20.2`) and `npx` on `PATH`.
20
20
  - An **SSH key authorized to push** to `xsitesinc/web-client-area` (push is over SSH).
21
- - A **Bitbucket API token** with pull-request write access (used only for the REST calls:
22
- reviewer lookup + PR creation). Store it in Ansible Vault or pass via environment — never commit it.
23
- - `ansible.builtin.uri` needs Python `urllib` (standard). No extra collections required.
21
+ - An **Atlassian API token** (`ATATT…`) for the account in `bitbucket_email`, carrying Bitbucket
22
+ scopes `read:user`, `read:repository`, `read:pullrequest`, `write:pullrequest`. Used only for the
23
+ REST calls (reviewer lookup + PR creation) via **HTTP Basic auth** (`email:token`) these tokens
24
+ are **not** Bearer tokens. Create at <https://id.atlassian.com/manage-profile/security/api-tokens>.
25
+ Provide via `--extra-vars` / Ansible Vault, or leave it blank and let the playbook fetch it from
26
+ 1Password (item `jenkins@xsites.co.il`, field `bitbucket_scoped_token`).
27
+ - A **Consul token** with `read` scope on `version/*` (v2 cluster) — used to derive the destination
28
+ branches from the release train. Provide via `-e consul_token=…`, `$CONSUL_HTTP_TOKEN`, or leave it
29
+ blank and let the playbook fetch it from 1Password (item `New_Consul_Token`, field `password`).
30
+ Not needed when you pass an explicit `dest_branches` override.
31
+ - A **signed-in `op` CLI** (`op signin`, or `OP_SERVICE_ACCOUNT_TOKEN`) if you rely on the 1Password
32
+ fallbacks above instead of passing the tokens explicitly.
33
+ - The **`community.general`** collection (for the `dict_kv` filter): `ansible-galaxy collection install -r requirements.yml`.
24
34
 
25
35
  ## Usage
26
36
 
@@ -30,32 +40,55 @@ Interactive (prompts for the inputs):
30
40
  ansible-playbook onboard-ca.yml
31
41
  ```
32
42
 
33
- Non-interactive (for CI / wrapping playbooks) — extra-vars skip the prompts:
43
+ Non-interactive (for CI / wrapping playbooks) — extra-vars skip the prompts. Destination
44
+ branches are derived from Consul, and both tokens fall back to 1Password, so the minimal run is:
34
45
 
35
46
  ```bash
36
47
  ansible-playbook onboard-ca.yml \
37
48
  -e brand_name="Zenith Horizon Group" \
38
- -e jira_key=ATLP-47067 \
49
+ -e jira_key_client_branding=ATLP-47067
50
+ ```
51
+
52
+ Passing the tokens explicitly (skips the 1Password lookups) and pinning the branches:
53
+
54
+ ```bash
55
+ ansible-playbook onboard-ca.yml \
56
+ -e brand_name="Zenith Horizon Group" \
57
+ -e jira_key_client_branding=ATLP-47067 \
39
58
  -e dest_branches=develop,master-26.9 \
59
+ -e consul_token="$CONSUL_HTTP_TOKEN" \
60
+ -e bitbucket_email=jenkins@xsites.co.il \
40
61
  -e bitbucket_token="$BITBUCKET_TOKEN"
41
62
  ```
42
63
 
64
+ Note: pass token values literally (or via a shell variable as above). Do **not** prefix a raw
65
+ `ATATT…` token with `$` — the shell would try to expand it as a variable and send an empty token.
66
+
43
67
  ## Inputs
44
68
 
45
- | var | required | example | notes |
46
- |-----|----------|---------|-------|
47
- | `brand_name` | yes | `Zenith Horizon Group` | display name → PR title |
48
- | `jira_key` | yes | `ATLP-47067` | PR title prefix + branch name |
49
- | `dest_branches` | yes | `develop,master-26.9` | comma-separated; **one PR per branch** |
50
- | `bitbucket_token` | yes | *(secret)* | Bearer token for the REST calls |
51
- | `brand_assets_folder` | no | `zenithhorizongroup` | folder name → CLI `--brand_assets_folder`, branch name; prompted separately. Blank ⇒ derived from `brand_name` (lowercased, spaces removed) |
52
- | `reviewers` | no | *(auto)* | default: repo's Bitbucket default-reviewers (author excluded); override with `[{account_id: "..."}]` |
53
- | `antelope_cli_version` | no | `1.2.0` | pinned published CLI version |
54
- | `work_dir` | no | `/tmp/onboard-ca/web-client-area` | clone location |
55
- | `git_user_name` / `git_user_email` | no | | commit identity |
69
+ | var | required | example | notes |
70
+ |-----|----------|---------------------------------------------------|-------|
71
+ | `brand_name` | yes | `Zenith Horizon Group` | display name → PR title |
72
+ | `jira_key_client_branding` | yes | `ATLP-47067` | PR title prefix + branch name |
73
+ | `bitbucket_token` | no* | *(secret)* | Atlassian API token (`ATATT…`) for the REST calls; sent via Basic auth. *Blank ⇒ fetched from 1Password (`jenkins@xsites.co.il` / `bitbucket_scoped_token`)* |
74
+ | `bitbucket_email` | yes | `jenkins@xsites.co.il` | account that owns the token (Basic-auth username); defaults to the Jenkins service account |
75
+ | `dest_branches` | no | `develop,master-26.9` | comma-separated override; **blank ⇒ derived from Consul** (see below). **One PR per branch** |
76
+ | `consul_token` | no* | *(secret)* | read scope on `version/*`. *Blank ⇒ `$CONSUL_HTTP_TOKEN`, else 1Password (`New_Consul_Token` / `password`)*. Unused when `dest_branches` is set |
77
+ | `brand_assets_folder` | no | `zenithhorizongroup` | folder name CLI `--brand_assets_folder`, branch name; prompted separately. Blank ⇒ derived from `brand_name` (lowercased, spaces removed) |
78
+ | `reviewers` | no | *(auto)* | default: repo's Bitbucket default-reviewers (author excluded); override with `[{account_id: "..."}]` |
79
+ | `consul_url` | no | `https://consul.xsites.xyz` | Consul base URL for the version lookup |
80
+ | `onepassword_bitbucket_item` / `_field` | no | `jenkins@xsites.co.il` / `bitbucket_scoped_token` | 1Password source for the Bitbucket token |
81
+ | `onepassword_consul_item` / `_field` | no | `New_Consul_Token` / `password` | 1Password source for the Consul token |
82
+ | `antelope_cli_version` | no | `1.2.3` | pinned published CLI version |
83
+ | `work_dir` | no | `/tmp/onboard-ca/web-client-area` | clone location |
84
+ | `git_user_name` / `git_user_email` | no | | commit identity |
56
85
 
57
86
  ## Behavior notes
58
87
 
88
+ - **Destination branches are derived from the release train in Consul** (unless `dest_branches`
89
+ is passed). `develop` is always onboarded; `master-<version/prod>` tracks the live release; and
90
+ while a new release is staged (`version/staging != version/prod`) its `master-<version/staging>`
91
+ is onboarded too. Once staging has shipped (`staging == prod`) there is a single master branch.
59
92
  - **One PR per destination branch.** `develop` → `feature/<jira>-onboarding-<folder>`;
60
93
  `master-*`/`release-*` → `hotfix/<jira>-onboarding-<folder>`. Each branch is based on its own
61
94
  destination so diverged histories don't bleed across PRs.
@@ -1,19 +1,27 @@
1
1
  ---
2
2
  # Onboard a Client Area brand into web-client-area and open the PR(s).
3
3
  #
4
+ # Destination branches are derived from the release train in Consul (no manual input):
5
+ # develop is always onboarded; master-<version/prod> tracks the live release; while a
6
+ # new release is staged (version/staging != version/prod) its master-<version/staging>
7
+ # exists too and is onboarded as well. Pass -e dest_branches=... to override the lookup.
8
+ #
4
9
  # Interactive:
5
10
  # ansible-playbook onboard-ca.yml
6
11
  #
7
12
  # Non-interactive (e.g. from another playbook / CI), pass everything as extra-vars:
8
- # ansible-playbook onboard-ca.yml \
9
- # -e brand_name="Zenith Horizon Group" \
10
- # -e brand_assets_folder=zenithhorizongroup \
11
- # -e jira_key=ATLP-47067 \
12
- # -e dest_branches=develop,master-26.9 \
13
- # -e bitbucket_token="$BITBUCKET_TOKEN"
14
- #
13
+ # ansible-playbook onboard-ca.yml \
14
+ # -e brand_name="Eldadtestbrand" \
15
+ # -e brand_assets_folder=eldadtestbrand \
16
+ # -e jira_key_client_branding=ATLP-47067 \
17
+ # -e consul_token="$CONSUL_HTTP_TOKEN" \ # read scope on version/*; used to derive dest branches
18
+ # -e bitbucket_email=jenkins@xsites.co.il \
19
+ # -e bitbucket_token=ATATT3x... # Atlassian API token WITHOUT a leading '$' (it is a literal, not a shell var)
20
+
15
21
  # Preconditions on the control node: git + Node (>=16) + npx, an SSH key authorized to
16
- # push to xsitesinc/web-client-area, and a Bitbucket API token with PR write access.
22
+ # push to xsitesinc/web-client-area, and an Atlassian API token (ATATT…) owned by
23
+ # bitbucket_email, carrying Bitbucket scopes read:user / read:repository /
24
+ # read+write:pullrequest. The token is used via HTTP Basic auth (email:token).
17
25
  - name: Onboard a Client Area brand (web-client-area)
18
26
  hosts: localhost
19
27
  connection: local
@@ -27,14 +35,16 @@
27
35
  prompt: "Brand assets folder name — the src/assets/other/<folder> (e.g. zenithhorizongroup); leave blank to derive from the display name"
28
36
  default: ""
29
37
  private: false
30
- - name: jira_key
38
+ - name: jira_key_client_branding
31
39
  prompt: "Jira key (e.g. ATLP-47067)"
32
40
  private: false
33
- - name: dest_branches
34
- prompt: "Destination branches, comma-separated (e.g. develop,master-26.9)"
41
+ - name: bitbucket_email
42
+ prompt: "Atlassian account email that owns the API token (Basic-auth username)"
43
+ default: "jenkins@xsites.co.il"
35
44
  private: false
36
45
  - name: bitbucket_token
37
- prompt: "Bitbucket API token"
46
+ prompt: "Atlassian API token (ATATT…); leave blank to fetch from 1Password (item jenkins@xsites.co.il)"
47
+ default: ""
38
48
  private: true
39
49
 
40
50
  roles:
@@ -0,0 +1,8 @@
1
+ ---
2
+ # Ansible collection dependencies for the onboarding-ca playbook.
3
+ # Install with: ansible-galaxy collection install -r requirements.yml
4
+ collections:
5
+ # Provides the community.general.dict_kv filter used to build the Bitbucket
6
+ # reviewers array (roles/onboard_ca/tasks/main.yml).
7
+ - name: community.general
8
+ version: ">=8.0.0"
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  # Pinned published antelope-cli version that provides the `onboarding-ca` command.
3
- antelope_cli_version: "1.2.0"
3
+ antelope_cli_version: "1.2.3"
4
4
 
5
5
  # Where web-client-area is cloned on the control node.
6
6
  work_dir: "/tmp/onboard-ca/web-client-area"
@@ -16,9 +16,34 @@ brand_assets_folder: ""
16
16
  reviewers: []
17
17
 
18
18
  # Identity used for the commit (control-node git may have no global config).
19
- git_user_name: "Antelope Onboarding Bot"
20
- git_user_email: "devops@antelopesystem.com"
19
+ git_user_name: "Jenkins"
20
+ git_user_email: "jenkins@xsites.co.il"
21
21
 
22
- # Bitbucket API token (Bearer) for the reviewer lookup + PR creation.
23
- # Provide via --extra-vars or Ansible Vault; never commit a real value.
22
+ # Atlassian API token (ATATT…) for the reviewer lookup + PR creation. Sent via HTTP
23
+ # Basic auth together with bitbucket_email these tokens are NOT Bearer tokens and
24
+ # must carry Bitbucket scopes (read:user, read:repository, read/write:pullrequest).
25
+ # Empty => fetched from 1Password (see onepassword_bitbucket_* below). Provide via
26
+ # --extra-vars or Ansible Vault to skip the lookup; never commit a real value.
24
27
  bitbucket_token: ""
28
+
29
+ # Email of the Atlassian account that owns bitbucket_token (the Basic-auth username).
30
+ # Defaults to the Jenkins service account; override via --extra-vars for a different account.
31
+ bitbucket_email: "jenkins@xsites.co.il"
32
+
33
+ # Consul token with read scope on version/* (v2 cluster). Used to derive the destination
34
+ # branches from the release train (version/prod, version/staging). Empty => resolved from
35
+ # $CONSUL_HTTP_TOKEN, else from 1Password (see onepassword_consul_* below). Not needed when
36
+ # an explicit dest_branches override is passed. Never commit a real value.
37
+ consul_token: ""
38
+
39
+ # Explicit destination-branch override (comma-separated). Empty => derived from Consul:
40
+ # develop + master-<version/prod> [+ master-<version/staging> while a release is staged].
41
+ # Set this to bypass the Consul lookup entirely, e.g. dest_branches=develop,master-26.9.
42
+ dest_branches: ""
43
+
44
+ # 1Password fallbacks — used only when the corresponding token is not otherwise provided.
45
+ # Require a signed-in `op` CLI on the control node (op signin, or OP_SERVICE_ACCOUNT_TOKEN).
46
+ onepassword_bitbucket_item: "jenkins@xsites.co.il"
47
+ onepassword_bitbucket_field: "bitbucket_scoped_token"
48
+ onepassword_consul_item: "New_Consul_Token"
49
+ onepassword_consul_field: "password"
@@ -1,12 +1,80 @@
1
1
  ---
2
+ # --- Resolve secrets: prefer the provided value, else fall back to 1Password (op CLI) ---
3
+ # The op fallback needs a signed-in `op` on the control node (op signin, or
4
+ # OP_SERVICE_ACCOUNT_TOKEN). A missing/failed op is tolerated here; the validate step
5
+ # below reports the still-empty token with a clear message.
6
+ - name: Fetch the Bitbucket API token from 1Password
7
+ when: (bitbucket_token | default('') | length) == 0
8
+ ansible.builtin.command:
9
+ argv:
10
+ - op
11
+ - item
12
+ - get
13
+ - "{{ onepassword_bitbucket_item }}"
14
+ - "--fields"
15
+ - "label={{ onepassword_bitbucket_field }}"
16
+ - "--reveal"
17
+ register: op_bitbucket
18
+ changed_when: false
19
+ failed_when: false
20
+ no_log: true
21
+
22
+ - name: Adopt the 1Password Bitbucket token
23
+ when:
24
+ - (bitbucket_token | default('') | length) == 0
25
+ - op_bitbucket is not skipped
26
+ - (op_bitbucket.rc | default(1)) == 0
27
+ - (op_bitbucket.stdout | default('') | length) > 0
28
+ ansible.builtin.set_fact:
29
+ bitbucket_token: "{{ op_bitbucket.stdout | trim }}"
30
+ no_log: true
31
+
32
+ # Consul token is only needed when destination branches are derived (no dest_branches override).
33
+ - name: Resolve the Consul token from the environment
34
+ when: (consul_token | default('') | length) == 0
35
+ ansible.builtin.set_fact:
36
+ consul_token: "{{ lookup('env', 'CONSUL_HTTP_TOKEN') }}"
37
+ no_log: true
38
+
39
+ - name: Fetch the Consul token from 1Password
40
+ when:
41
+ - (consul_token | default('') | length) == 0
42
+ - (dest_branches | default('') | length) == 0
43
+ ansible.builtin.command:
44
+ argv:
45
+ - op
46
+ - item
47
+ - get
48
+ - "{{ onepassword_consul_item }}"
49
+ - "--fields"
50
+ - "label={{ onepassword_consul_field }}"
51
+ - "--reveal"
52
+ register: op_consul
53
+ changed_when: false
54
+ failed_when: false
55
+ no_log: true
56
+
57
+ - name: Adopt the 1Password Consul token
58
+ when:
59
+ - (consul_token | default('') | length) == 0
60
+ - op_consul is not skipped
61
+ - (op_consul.rc | default(1)) == 0
62
+ - (op_consul.stdout | default('') | length) > 0
63
+ ansible.builtin.set_fact:
64
+ consul_token: "{{ op_consul.stdout | trim }}"
65
+ no_log: true
66
+
2
67
  - name: Validate required inputs
3
68
  ansible.builtin.assert:
4
69
  that:
5
70
  - brand_name is defined and (brand_name | length) > 0
6
- - jira_key is defined and (jira_key | length) > 0
7
- - dest_branches is defined and (dest_branches | length) > 0
71
+ - jira_key_client_branding is defined and (jira_key_client_branding | length) > 0
8
72
  - bitbucket_token is defined and (bitbucket_token | length) > 0
9
- fail_msg: "brand_name, jira_key, dest_branches and bitbucket_token are all required."
73
+ - bitbucket_email is defined and (bitbucket_email | length) > 0
74
+ fail_msg: >-
75
+ brand_name, jira_key_client_branding and bitbucket_email are required, and bitbucket_token must be
76
+ provided (via --extra-vars / prompt) or resolvable from 1Password item
77
+ '{{ onepassword_bitbucket_item }}' field '{{ onepassword_bitbucket_field }}' (op signin).
10
78
 
11
79
  - name: Compute effective brand assets folder
12
80
  ansible.builtin.set_fact:
@@ -15,10 +83,55 @@
15
83
  if (brand_assets_folder | default('') | length) > 0
16
84
  else (brand_name | lower | regex_replace(' ', '')) }}
17
85
 
18
- - name: Normalize destination branch list
86
+ # --- Destination branches: derived from the release train in Consul ------------------
87
+ # develop is always onboarded. master-<version/prod> tracks the live release; while a new
88
+ # release is staged (version/staging != version/prod) its master-<version/staging> exists
89
+ # too and is onboarded as well. Once staging == prod (the release has shipped) there is a
90
+ # single master branch. An explicit dest_branches extra-var bypasses the lookup entirely.
91
+ - name: Resolve destination branches from Consul
92
+ when: (dest_branches | default('') | length) == 0
93
+ block:
94
+ - name: Consul token is required to derive destination branches
95
+ ansible.builtin.assert:
96
+ that:
97
+ - consul_token is defined and (consul_token | length) > 0
98
+ fail_msg: >-
99
+ consul_token is required to read version/prod and version/staging. Provide it via
100
+ -e consul_token=..., $CONSUL_HTTP_TOKEN, or 1Password item '{{ onepassword_consul_item }}'
101
+ (op signin); or pass -e dest_branches=develop,master-XX.Y to bypass the Consul lookup.
102
+
103
+ - name: Read the prod release version from Consul
104
+ ansible.builtin.uri:
105
+ url: "{{ consul_url }}/v1/kv/version/prod?raw=1"
106
+ headers:
107
+ X-Consul-Token: "{{ consul_token }}"
108
+ return_content: true
109
+ register: consul_prod
110
+
111
+ - name: Read the staging release version from Consul
112
+ ansible.builtin.uri:
113
+ url: "{{ consul_url }}/v1/kv/version/staging?raw=1"
114
+ headers:
115
+ X-Consul-Token: "{{ consul_token }}"
116
+ return_content: true
117
+ register: consul_staging
118
+
119
+ - name: Derive the destination branch list from the release versions
120
+ ansible.builtin.set_fact:
121
+ dest_branch_list: >-
122
+ {{ ['develop', 'master-' + (consul_prod.content | trim)]
123
+ + (['master-' + (consul_staging.content | trim)]
124
+ if (consul_staging.content | trim) != (consul_prod.content | trim) else []) }}
125
+
126
+ - name: Normalize destination branch list (explicit override)
127
+ when: (dest_branches | default('') | length) > 0
19
128
  ansible.builtin.set_fact:
20
129
  dest_branch_list: "{{ dest_branches.split(',') | map('trim') | reject('equalto', '') | list }}"
21
130
 
131
+ - name: Show the resolved destination branches
132
+ ansible.builtin.debug:
133
+ msg: "Destination branches: {{ dest_branch_list }}"
134
+
22
135
  # --- Reviewers: pull from the repo's Bitbucket default-reviewers config -------------
23
136
  - name: Resolve reviewers from repo default-reviewers config
24
137
  when: (reviewers | length) == 0
@@ -26,29 +139,33 @@
26
139
  - name: Identify the authenticating Bitbucket account (PR author)
27
140
  ansible.builtin.uri:
28
141
  url: "{{ api_base }}/user"
29
- headers:
30
- Authorization: "Bearer {{ bitbucket_token }}"
142
+ url_username: "{{ bitbucket_email }}"
143
+ url_password: "{{ bitbucket_token }}"
144
+ force_basic_auth: true
31
145
  return_content: true
32
146
  register: bb_user
33
147
 
34
148
  - name: Fetch the repository default reviewers
35
149
  ansible.builtin.uri:
36
150
  url: "{{ api_base }}/repositories/{{ workspace }}/{{ repo_slug }}/default-reviewers?pagelen=100"
37
- headers:
38
- Authorization: "Bearer {{ bitbucket_token }}"
151
+ url_username: "{{ bitbucket_email }}"
152
+ url_password: "{{ bitbucket_token }}"
153
+ force_basic_auth: true
39
154
  return_content: true
40
155
  register: bb_default_reviewers
41
156
 
42
157
  # Bitbucket rejects a PR whose author is also a reviewer, so drop the authenticating account.
43
158
  # The REST API does not auto-apply default reviewers, so we pass them explicitly.
44
159
  # Bitbucket's PR-create reviewers array is keyed by uuid; exclude the author by account_id.
160
+ # Built in a single expression (not an accumulating set_fact loop, which is nondeterministic).
45
161
  - name: Build reviewers list (excluding the PR author)
46
162
  ansible.builtin.set_fact:
47
- reviewers: "{{ reviewers + [{'uuid': item.uuid}] }}"
48
- loop: "{{ bb_default_reviewers.json['values'] }}"
49
- when: item.account_id != bb_user.json.account_id
50
- loop_control:
51
- label: "{{ item.display_name | default(item.uuid) }}"
163
+ reviewers: >-
164
+ {{ bb_default_reviewers.json['values']
165
+ | rejectattr('account_id', 'equalto', bb_user.json.account_id)
166
+ | map(attribute='uuid')
167
+ | map('community.general.dict_kv', 'uuid')
168
+ | list }}
52
169
 
53
170
  - name: Ensure clone parent directory exists
54
171
  ansible.builtin.file:
@@ -7,7 +7,7 @@
7
7
 
8
8
  - name: "[{{ dest_branch }}] Set source branch"
9
9
  ansible.builtin.set_fact:
10
- source_branch: "{{ branch_prefix }}/{{ jira_key }}-onboarding-{{ brand_assets_folder }}"
10
+ source_branch: "{{ branch_prefix }}/{{ jira_key_client_branding }}-onboarding-{{ brand_assets_folder }}"
11
11
 
12
12
  - name: "[{{ dest_branch }}] Fetch all remote branches"
13
13
  ansible.builtin.command:
@@ -63,7 +63,7 @@
63
63
  ansible.builtin.command:
64
64
  cmd: >-
65
65
  git -c user.name="{{ git_user_name }}" -c user.email="{{ git_user_email }}"
66
- commit -m "{{ jira_key }}: On-Boarding {{ brand_name }} Client Branding"
66
+ commit -m "{{ jira_key_client_branding }}: On-Boarding {{ brand_name }} Client Branding"
67
67
  chdir: "{{ work_dir }}"
68
68
  when: staged.rc == 1
69
69
 
@@ -77,12 +77,13 @@
77
77
  ansible.builtin.uri:
78
78
  url: "{{ api_base }}/repositories/{{ workspace }}/{{ repo_slug }}/pullrequests"
79
79
  method: POST
80
- headers:
81
- Authorization: "Bearer {{ bitbucket_token }}"
80
+ url_username: "{{ bitbucket_email }}"
81
+ url_password: "{{ bitbucket_token }}"
82
+ force_basic_auth: true
82
83
  body_format: json
83
84
  body:
84
- title: "{{ jira_key }}: On-Boarding {{ brand_name }} Client Branding"
85
- description: "{{ jira_key }}: On-Boarding {{ brand_name }} Client Branding"
85
+ title: "{{ jira_key_client_branding }}: On-Boarding {{ brand_name }} Client Branding"
86
+ description: "{{ jira_key_client_branding }}: On-Boarding {{ brand_name }} Client Branding"
86
87
  source:
87
88
  branch:
88
89
  name: "{{ source_branch }}"
@@ -4,3 +4,6 @@ api_base: "https://api.bitbucket.org/2.0"
4
4
  workspace: "xsitesinc"
5
5
  repo_slug: "web-client-area"
6
6
  repo_ssh_url: "git@bitbucket.org:xsitesinc/web-client-area.git"
7
+
8
+ # Consul (v2 cluster) — source of the release train used to derive destination branches.
9
+ consul_url: "https://consul.xsites.xyz"
@@ -1,21 +1,22 @@
1
- let brand = {
2
- brandName: '',
3
- kebabCaseName: '',
4
- connectedBrandName: '',
5
- color: '',
6
- languages: null,
7
- favicon: '',
8
- type: '',
9
- FCMSenderId: '',
10
- logo: ''
11
- };
12
-
13
- function updateBrand(updatedBrand) {
14
- brand = { ...brand, ...updatedBrand };
15
- }
16
-
17
- function getBrand() {
18
- return brand;
19
- }
20
-
21
- module.exports = { updateBrand, getBrand };
1
+ let brand = {
2
+ brandId: '',
3
+ brandName: '',
4
+ connectedBrandName: '',
5
+ color: '',
6
+ logo: '',
7
+ languages: null,
8
+ favicon: '',
9
+ FCMSenderId: '',
10
+ type: '',
11
+ baseUrl: ''
12
+ };
13
+
14
+ function updateBrand(updatedBrand) {
15
+ brand = { ...brand, ...updatedBrand };
16
+ }
17
+
18
+ function getBrand() {
19
+ return brand;
20
+ }
21
+
22
+ module.exports = { updateBrand, getBrand };
@@ -0,0 +1,33 @@
1
+ const fs = require('fs');
2
+ const chalk = require('chalk');
3
+ const brandModule = require('./brand.js');
4
+
5
+ const brandLocationsPath = 'BRAND_LOCATIONS.md';
6
+
7
+ // Append a sprint-tracking row "<brand_name> → <brand_id>" to BRAND_LOCATIONS.md,
8
+ // mirroring the migrate-brand-to-id skill (today's date, QA sign-off left blank).
9
+ function start() {
10
+ return new Promise((resolve, reject) => {
11
+ try {
12
+ if (!fs.existsSync(brandLocationsPath)) {
13
+ console.log(chalk.yellow(`${brandLocationsPath} not found — skipping tracking row.`));
14
+ return resolve(null);
15
+ }
16
+ const { brandName, brandId } = brandModule.getBrand();
17
+ const date = new Date().toISOString().slice(0, 10);
18
+ const cell = `${brandName} → ${brandId}`;
19
+ const row = `| ${date} | ${cell.padEnd(27)} | |`;
20
+
21
+ let data = fs.readFileSync(brandLocationsPath, 'utf8');
22
+ if (!data.endsWith('\n')) data += '\n';
23
+ data += row + '\n';
24
+ fs.writeFileSync(brandLocationsPath, data, 'utf8');
25
+ console.log(chalk.cyan(`BRAND_LOCATIONS.md updated: ${cell}`));
26
+ resolve(null);
27
+ } catch (error) {
28
+ reject(error);
29
+ }
30
+ });
31
+ }
32
+
33
+ module.exports = { start };
@@ -1,38 +1,40 @@
1
- const fs = require('fs');
2
- const brandModule = require('./brand.js');
3
- const chalk = require('chalk');
4
- const configsFilePath = 'src/configs/configs.ts';
5
-
6
- // Same output as onboarding/createConfig.js FCMSenderId/type are resolved upfront (flags/prompt).
7
- function start() {
8
- return new Promise((resolve, reject) => {
9
- fs.writeFile(configsFilePath, updateConfigFile(), 'utf8', (error) => {
10
- if (error) {
11
- reject(error);
12
- } else {
13
- console.log(chalk.cyan('configs.ts file updated successfully!'));
14
- resolve(null);
15
- }
16
- });
17
- });
18
- }
19
-
20
- const updateConfigFile = () => {
21
- const brand = brandModule.getBrand();
22
- return `export const configs: WebCrmConfig = {
23
- baseUrl: 'https://dev-apicrm.antelopesystem.com/SignalsCRM/',
24
- appId: 0,
25
- type: '${brand.type}',
26
- defaultLanguage: 'en',
27
- gaTrackingID: 'UA-70456753-2',
28
- colorTheme: 'theme-${brand.connectedBrandName}',
29
- templateTheme: 'theme-template-${brand.connectedBrandName}',
30
- title: '${brand.brandName}',
31
- favicon: '${brand.favicon}',
32
- affiliatesApiUrl: '',
33
- FCMSenderId: '${brand.FCMSenderId}',
34
- brandAssetsFolder: '${brand.connectedBrandName}',
35
- };`;
36
- };
37
-
38
- module.exports = { start };
1
+ const fs = require('fs');
2
+ const brandModule = require('./brand.js');
3
+ const chalk = require('chalk');
4
+ const configsFilePath = 'src/configs/configs.ts';
5
+
6
+ // Writes configs.ts (devops copy-paste this into the env's Consul config).
7
+ // Brand references use the Antelope ID (connectedBrandName = brand_id); baseUrl comes from the flag.
8
+ function start() {
9
+ return new Promise((resolve, reject) => {
10
+ fs.mkdirSync('src/configs', { recursive: true });
11
+ fs.writeFile(configsFilePath, updateConfigFile(), 'utf8', (error) => {
12
+ if (error) {
13
+ reject(error);
14
+ } else {
15
+ console.log(chalk.cyan('configs.ts file updated successfully!'));
16
+ resolve(null);
17
+ }
18
+ });
19
+ });
20
+ }
21
+
22
+ const updateConfigFile = () => {
23
+ const brand = brandModule.getBrand();
24
+ return `export const configs: WebCrmConfig = {
25
+ baseUrl: '${brand.baseUrl}',
26
+ appId: 0,
27
+ type: '${brand.type}',
28
+ defaultLanguage: 'en',
29
+ gaTrackingID: 'UA-70456753-2',
30
+ colorTheme: 'theme-${brand.connectedBrandName}',
31
+ templateTheme: 'theme-template-${brand.connectedBrandName}',
32
+ title: '${brand.brandName}',
33
+ favicon: '${brand.favicon}',
34
+ logo: '${brand.logo}',
35
+ FCMSenderId: '${brand.FCMSenderId}',
36
+ brandAssetsFolder: '${brand.connectedBrandName}'
37
+ };`;
38
+ };
39
+
40
+ module.exports = { start };
@@ -1,157 +1,171 @@
1
- const readline = require('readline');
2
- const chalk = require('chalk');
3
- const brandModule = require('./brand.js');
4
- const variablesColors = require('./variablesColors.js');
5
- const themes = require('./themes.js');
6
- const translations = require('./translations.js');
7
- const createConfig = require('./createConfig.js');
8
-
9
- const ALLOWED_TYPES = ['CORP_FL', 'IB_FL', 'CORP_AFF_FL', 'CORP_AFF_IB_PORTAL', 'CORP_AFF_MSQ'];
10
-
11
- const HELP = `
12
- Usage: ant-cli onboarding-crm --brand_name <name> [options]
13
-
14
- Onboards a CRM brand in the current web-crm checkout same edits as "onboarding", but
15
- every value is taken from flags (interactive prompts are only a fallback in a TTY):
16
- src/ng1/assets/css/sass/variables-colors.scss (+ $colors entry and color shades)
17
- src/ng1/assets/css/sass/materialism/themes.scss (+ theme maps and .theme-template block)
18
- src/assets/brands/<folder>/languages/<lang>.json (one empty file per language)
19
- src/configs/configs.ts (rewritten for the brand)
20
-
21
- Options:
22
- --brand_name <name> Display name configs title, e.g. "Zenith Horizon Group" (required)
23
- --brand_assets_folder <f> Brand assets folder / connectedBrandName. Default: derived from
24
- --brand_name (lowercased, spaces removed)
25
- --color <hex> Brand hex color, e.g. "#1a2b3c". Empty default palette ($corp)
26
- --logo_url <url> Logo image URL (black background) (required)
27
- --favicon_url <url> Favicon image URL (required)
28
- --languages <csv> 2-letter language codes, comma-separated, e.g. "en,ar,de" (required)
29
- --fcm_sender_id <id> Firebase FCMSenderId (required)
30
- --type <type> One of: ${ALLOWED_TYPES.join(', ')} (required)
31
- -h, --help Show this help
32
-
33
- Run from the root of a web-crm checkout — files are written relative to the current directory.
34
- `;
35
-
36
- const FLAGS = {
37
- '--brand_name': 'brandName',
38
- '--brand_assets_folder': 'brandAssetsFolder',
39
- '--color': 'color',
40
- '--logo_url': 'logo',
41
- '--favicon_url': 'favicon',
42
- '--languages': 'languages',
43
- '--fcm_sender_id': 'fcmSenderId',
44
- '--type': 'type'
45
- };
46
-
47
- function parseArgs(argv) {
48
- const opts = {};
49
- for (let i = 0; i < argv.length; i++) {
50
- const a = argv[i];
51
- if (a === '--help' || a === '-h') opts.help = true;
52
- else if (FLAGS[a]) opts[FLAGS[a]] = argv[++i];
53
- }
54
- return opts;
55
- }
56
-
57
- const ask = (rl, question) => new Promise((resolve) => rl.question(question, (a) => resolve(a)));
58
-
59
- // Resolve a value: prefer the flag; otherwise prompt (interactive only). Returns undefined
60
- // when absent and non-interactive, so the caller can report it as a missing required field.
61
- async function resolve(opts, key, promptText, interactive, rl) {
62
- if (opts[key] !== undefined) return opts[key];
63
- if (interactive) return ask(rl, promptText);
64
- return undefined;
65
- }
66
-
67
- async function run() {
68
- const opts = parseArgs(process.argv.slice(3));
69
- if (opts.help) {
70
- console.log(HELP);
71
- return;
72
- }
73
-
74
- console.log(chalk.underline(chalk.cyan('Antelope CRM on-boarding\n')));
75
-
76
- // Interactive only in a real terminal and when not forced non-interactive (CI / Ansible).
77
- const interactive = Boolean(process.stdin.isTTY) && !process.env.CI;
78
- const rl = interactive ? readline.createInterface({ input: process.stdin, output: process.stdout }) : null;
79
-
80
- try {
81
- const brandName = await resolve(opts, 'brandName', "What is the new brand's name?\n", interactive, rl);
82
- const kebabCaseName = brandName ? brandName.toLowerCase().replace(/ /g, '-') : '';
83
- const connectedBrandName = opts.brandAssetsFolder
84
- ? opts.brandAssetsFolder.trim().toLowerCase().replace(/ /g, '')
85
- : (brandName ? brandName.toLowerCase().replace(/ /g, '') : '');
86
-
87
- // Color is optional (empty ⇒ default palette).
88
- const color = opts.color !== undefined
89
- ? opts.color
90
- : (interactive ? await ask(rl, `Please set a color for "${kebabCaseName}", use hex value, leave empty for default.\n`) : '');
91
-
92
- const logo = await resolve(opts, 'logo', 'Please enter the URL to the logo image suitable for a black background:\n', interactive, rl);
93
- const favicon = await resolve(opts, 'favicon', 'Please provide the url to your favicon image:\n', interactive, rl);
94
- const languagesInput = await resolve(opts, 'languages', 'Enter the languages (2-letter codes) separated by commas:\n', interactive, rl);
95
- const fcmSenderId = await resolve(opts, 'fcmSenderId', 'Enter the FCMSenderId value (see https://xsites.atlassian.net/wiki/spaces/IKB/pages/777650347/Onboarding+Branding+Manual+for+devs#Firebase): ', interactive, rl);
96
- const typeInput = await resolve(opts, 'type', `Enter the type value (allowed values: ${ALLOWED_TYPES.join(', ')}): `, interactive, rl);
97
-
98
- // Validate required fields.
99
- const missing = [];
100
- if (!brandName) missing.push('--brand_name');
101
- if (!logo) missing.push('--logo_url');
102
- if (!favicon) missing.push('--favicon_url');
103
- if (!languagesInput) missing.push('--languages');
104
- if (!fcmSenderId) missing.push('--fcm_sender_id');
105
- if (!typeInput) missing.push('--type');
106
- if (missing.length) {
107
- console.error(chalk.red(`Error: missing required value(s): ${missing.join(', ')}`));
108
- console.log(HELP);
109
- process.exitCode = 1;
110
- return;
111
- }
112
-
113
- const type = typeInput.toUpperCase();
114
- if (!ALLOWED_TYPES.includes(type)) {
115
- console.error(chalk.red(`Error: invalid --type "${typeInput}". Allowed: ${ALLOWED_TYPES.join(', ')}`));
116
- process.exitCode = 1;
117
- return;
118
- }
119
-
120
- const languages = languagesInput.trim().split(',').map((l) => l.trim()).filter(Boolean);
121
- if (!languages.length) {
122
- console.error(chalk.red('Error: --languages must contain at least one 2-letter code.'));
123
- process.exitCode = 1;
124
- return;
125
- }
126
-
127
- brandModule.updateBrand({
128
- brandName,
129
- kebabCaseName,
130
- connectedBrandName,
131
- color: color || '',
132
- logo,
133
- favicon,
134
- languages,
135
- FCMSenderId: fcmSenderId,
136
- type
137
- });
138
-
139
- console.log(chalk.bold(chalk.yellow(`\nOnboarding "${brandName}" (folder: ${connectedBrandName})`)));
140
- console.log(chalk.bold(chalk.yellow('\nStep 1: Add to variables-colors.scss')));
141
- await variablesColors.start();
142
- console.log(chalk.bold(chalk.yellow('\nStep 2: Add to themes.scss')));
143
- await themes.start();
144
- console.log(chalk.bold(chalk.yellow('\nStep 3: Adding translations')));
145
- await translations.start();
146
- console.log(chalk.bold(chalk.yellow('\nStep 4: Editing the config file')));
147
- await createConfig.start();
148
- console.log(chalk.green('\nCRM onboarding completed!'));
149
- } catch (err) {
150
- console.error(chalk.red(err) + '\n Please delete files and code added by this failed session.');
151
- process.exitCode = 1;
152
- } finally {
153
- if (rl) rl.close();
154
- }
155
- }
156
-
157
- run();
1
+ const readline = require('readline');
2
+ const chalk = require('chalk');
3
+ const brandModule = require('./brand.js');
4
+ const variablesColors = require('./variablesColors.js');
5
+ const themes = require('./themes.js');
6
+ const translations = require('./translations.js');
7
+ const createConfig = require('./createConfig.js');
8
+ const brandLocations = require('./brandLocations.js');
9
+
10
+ const ALLOWED_TYPES = ['CORP_FL', 'IB_FL', 'CORP_AFF_FL', 'CORP_AFF_IB_PORTAL', 'CORP_AFF_MSQ'];
11
+ const DEFAULT_BASE_URL = 'https://dev-apicrm.antelopesystem.com/SignalsCRM/';
12
+
13
+ const HELP = `
14
+ Usage: ant-cli onboarding-crm --brand_id <id> --brand_name <name> [options]
15
+
16
+ Onboards a CRM brand in the current web-crm checkout, keyed by the brand's Antelope ID.
17
+ All inputs come from flags (interactive prompts are only a fallback in a TTY):
18
+ src/ng1/assets/css/sass/variables-colors.scss (+ $<id> shades and $colors entry)
19
+ src/ng1/assets/css/sass/materialism/themes.scss (+ theme maps and .theme-template-<id> block)
20
+ src/assets/brands/<id>/languages/<lang>.json (one empty file per language)
21
+ src/configs/configs.ts (rewritten for the brand — copy into Consul)
22
+ BRAND_LOCATIONS.md (+ sprint tracking row "<name> -> <id>")
23
+
24
+ Options:
25
+ --brand_id <id> Antelope unique ID, e.g. "xxxxx" the code identifier used everywhere (required)
26
+ --brand_name <name> Human brand name recorded in the BRAND_LOCATIONS.md tracking row (required)
27
+ --color <hex> Brand hex color, e.g. "#1a2b3c". Empty => default palette ($corp)
28
+ --logo_url <url> Logo image URL (black background) (required)
29
+ --languages <csv> 2-letter language codes, comma-separated, e.g. "en,ar" (required)
30
+ --favicon_url <url> Favicon image URL → configs.ts favicon (required)
31
+ --fcm_sender_id <id> Firebase FCMSenderId → configs.ts (required)
32
+ --type <type> One of: ${ALLOWED_TYPES.join(', ')} → configs.ts (required)
33
+ --base_url <url> configs.ts baseUrl. Default: ${DEFAULT_BASE_URL}
34
+ -h, --help Show this help
35
+
36
+ Run from the root of a web-crm checkout — files are written relative to the current directory.
37
+ `;
38
+
39
+ const FLAGS = {
40
+ '--brand_id': 'brandId',
41
+ '--brand_name': 'brandName',
42
+ '--color': 'color',
43
+ '--logo_url': 'logo',
44
+ '--languages': 'languages',
45
+ '--favicon_url': 'favicon',
46
+ '--fcm_sender_id': 'fcmSenderId',
47
+ '--type': 'type',
48
+ '--base_url': 'baseUrl'
49
+ };
50
+
51
+ function parseArgs(argv) {
52
+ const opts = {};
53
+ for (let i = 0; i < argv.length; i++) {
54
+ const a = argv[i];
55
+ if (a === '--help' || a === '-h') opts.help = true;
56
+ else if (FLAGS[a]) opts[FLAGS[a]] = argv[++i];
57
+ }
58
+ return opts;
59
+ }
60
+
61
+ // Antelope IDs are lowercase alphanumeric; normalize defensively (trim, lowercase, drop spaces).
62
+ const normalizeId = (value) => value.trim().toLowerCase().replace(/ /g, '');
63
+
64
+ const ask = (rl, question) => new Promise((resolve) => rl.question(question, (a) => resolve(a)));
65
+
66
+ // Resolve a value: prefer the flag; otherwise prompt (interactive only). Returns undefined
67
+ // when absent and non-interactive, so the caller can report it as a missing required field.
68
+ async function resolve(opts, key, promptText, interactive, rl) {
69
+ if (opts[key] !== undefined) return opts[key];
70
+ if (interactive) return ask(rl, promptText);
71
+ return undefined;
72
+ }
73
+
74
+ async function run() {
75
+ const opts = parseArgs(process.argv.slice(3));
76
+ if (opts.help) {
77
+ console.log(HELP);
78
+ return;
79
+ }
80
+
81
+ console.log(chalk.underline(chalk.cyan('Antelope CRM on-boarding\n')));
82
+
83
+ // Interactive only in a real terminal and when not forced non-interactive (CI / Ansible).
84
+ const interactive = Boolean(process.stdin.isTTY) && !process.env.CI;
85
+ const rl = interactive ? readline.createInterface({ input: process.stdin, output: process.stdout }) : null;
86
+
87
+ try {
88
+ const brandIdRaw = await resolve(opts, 'brandId', "Enter the brand's Antelope ID (e.g. xxxxx):\n", interactive, rl);
89
+ const brandName = await resolve(opts, 'brandName', "What is the new brand's name?\n", interactive, rl);
90
+ const connectedBrandName = brandIdRaw ? normalizeId(brandIdRaw) : '';
91
+
92
+ // Color is optional (empty => default palette).
93
+ const color = opts.color !== undefined
94
+ ? opts.color
95
+ : (interactive ? await ask(rl, `Please set a color for "${connectedBrandName}", use hex value, leave empty for default.\n`) : '');
96
+
97
+ const logo = await resolve(opts, 'logo', 'Please enter the URL to the logo image suitable for a black background:\n', interactive, rl);
98
+ const favicon = await resolve(opts, 'favicon', 'Please provide the url to your favicon image:\n', interactive, rl);
99
+ const languagesInput = await resolve(opts, 'languages', 'Enter the languages (2-letter codes) separated by commas:\n', interactive, rl);
100
+ const fcmSenderId = await resolve(opts, 'fcmSenderId', 'Enter the FCMSenderId value (see https://xsites.atlassian.net/wiki/spaces/IKB/pages/777650347/Onboarding+Branding+Manual+for+devs#Firebase): ', interactive, rl);
101
+ const typeInput = await resolve(opts, 'type', `Enter the type value (allowed values: ${ALLOWED_TYPES.join(', ')}): `, interactive, rl);
102
+
103
+ // baseUrl is optional (defaults to the dev URL).
104
+ const baseUrl = opts.baseUrl !== undefined && opts.baseUrl !== ''
105
+ ? opts.baseUrl
106
+ : (interactive ? (await ask(rl, `Enter the configs.ts baseUrl (leave empty for ${DEFAULT_BASE_URL}):\n`)) || DEFAULT_BASE_URL : DEFAULT_BASE_URL);
107
+
108
+ // Validate required fields.
109
+ const missing = [];
110
+ if (!connectedBrandName) missing.push('--brand_id');
111
+ if (!brandName) missing.push('--brand_name');
112
+ if (!logo) missing.push('--logo_url');
113
+ if (!favicon) missing.push('--favicon_url');
114
+ if (!languagesInput) missing.push('--languages');
115
+ if (!fcmSenderId) missing.push('--fcm_sender_id');
116
+ if (!typeInput) missing.push('--type');
117
+ if (missing.length) {
118
+ console.error(chalk.red(`Error: missing required value(s): ${missing.join(', ')}`));
119
+ console.log(HELP);
120
+ process.exitCode = 1;
121
+ return;
122
+ }
123
+
124
+ const type = typeInput.toUpperCase();
125
+ if (!ALLOWED_TYPES.includes(type)) {
126
+ console.error(chalk.red(`Error: invalid --type "${typeInput}". Allowed: ${ALLOWED_TYPES.join(', ')}`));
127
+ process.exitCode = 1;
128
+ return;
129
+ }
130
+
131
+ const languages = languagesInput.trim().split(',').map((l) => l.trim()).filter(Boolean);
132
+ if (!languages.length) {
133
+ console.error(chalk.red('Error: --languages must contain at least one 2-letter code.'));
134
+ process.exitCode = 1;
135
+ return;
136
+ }
137
+
138
+ brandModule.updateBrand({
139
+ brandId: connectedBrandName,
140
+ brandName,
141
+ connectedBrandName,
142
+ color: color || '',
143
+ logo,
144
+ languages,
145
+ favicon,
146
+ FCMSenderId: fcmSenderId,
147
+ type,
148
+ baseUrl
149
+ });
150
+
151
+ console.log(chalk.bold(chalk.yellow(`\nOnboarding "${brandName}" (id: ${connectedBrandName})`)));
152
+ console.log(chalk.bold(chalk.yellow('\nStep 1: Add to variables-colors.scss')));
153
+ await variablesColors.start();
154
+ console.log(chalk.bold(chalk.yellow('\nStep 2: Add to themes.scss')));
155
+ await themes.start();
156
+ console.log(chalk.bold(chalk.yellow('\nStep 3: Adding translations')));
157
+ await translations.start();
158
+ console.log(chalk.bold(chalk.yellow('\nStep 4: Editing the config file')));
159
+ await createConfig.start();
160
+ console.log(chalk.bold(chalk.yellow('\nStep 5: Recording in BRAND_LOCATIONS.md')));
161
+ await brandLocations.start();
162
+ console.log(chalk.green('\nCRM onboarding completed!'));
163
+ } catch (err) {
164
+ console.error(chalk.red(err) + '\n Please delete files and code added by this failed session.');
165
+ process.exitCode = 1;
166
+ } finally {
167
+ if (rl) rl.close();
168
+ }
169
+ }
170
+
171
+ run();
@@ -1,92 +1,92 @@
1
- const fs = require('fs');
2
- const brandModule = require('./brand.js');
3
- const chalk = require('chalk');
4
- const themesPath = 'src/ng1/assets/css/sass/materialism/themes.scss';
5
-
6
- function addColorToMap(mapName, key, value, data) {
7
- const mapRegex = new RegExp(`\\$${mapName}:\\s*\\(([\\s\\S]*?)\\)`);
8
- const match = data.match(mapRegex);
9
- if (match) {
10
- const mapContent = match[1];
11
- const newMapContent = `$${mapName}: (${mapContent},\n '${key}': ${value})`;
12
- data = data.replace(mapRegex, newMapContent);
13
- } else {
14
- throw new Error(`Error: Could not find ${mapName} map in the file`);
15
- }
16
- return data;
17
- }
18
-
19
- // Same edits as onboarding/themes.js, driven by resolved brand state (no prompts).
20
- function start() {
21
- const brand = brandModule.getBrand();
22
- return new Promise((resolve, reject) => {
23
- const themeVar = `
24
- .theme-template-${brand.connectedBrandName} {
25
- #logo {
26
- display: inline-block;
27
- width: 240px;
28
- height: 120px;
29
- background: url('${brand.logo}') no-repeat center center;
30
- background-size: contain;
31
- }
32
- .navbar-toggle-container {
33
- background: #222C38;
34
-
35
- .icon-bar {
36
- background: #fff !important;
37
- }
38
- }
39
- .sidebar {
40
- background: #222C38;
41
- color: #ffffff;
42
-
43
- ul a,
44
- i {
45
- color: #ffffff;
46
- }
47
-
48
- .brand-logo,
49
- .brand-logo-text {
50
- display: none;
51
- }
52
- .user-logged-in:after {
53
- background: #26303C;
54
- }
55
- }
56
- }
57
- \n`;
58
- fs.readFile(themesPath, 'utf8', (err, data) => {
59
- if (err) {
60
- return reject(err);
61
- }
62
- try {
63
- data = addColorToMap(
64
- 'theme-colors',
65
- brand.connectedBrandName,
66
- brand.color ? `$${brand.connectedBrandName}` : '$corp',
67
- data
68
- );
69
- data = addColorToMap(
70
- 'theme-secondary-colors',
71
- brand.connectedBrandName,
72
- brand.color ? `'${brand.connectedBrandName}'` : "'corp'",
73
- data
74
- );
75
- } catch (mapError) {
76
- return reject(mapError);
77
- }
78
- const insertionPoint = data.lastIndexOf('@each $color-name');
79
- const newFileContent = data.slice(0, insertionPoint) + themeVar + data.slice(insertionPoint);
80
- fs.writeFile(themesPath, newFileContent, 'utf8', (error) => {
81
- if (error) {
82
- reject(error);
83
- } else {
84
- console.log(chalk.cyan('themes.scss file updated successfully!'));
85
- resolve(null);
86
- }
87
- });
88
- });
89
- });
90
- }
91
-
92
- module.exports = { start };
1
+ const fs = require('fs');
2
+ const brandModule = require('./brand.js');
3
+ const chalk = require('chalk');
4
+ const themesPath = 'src/ng1/assets/css/sass/materialism/themes.scss';
5
+
6
+ function addColorToMap(mapName, key, value, data) {
7
+ const mapRegex = new RegExp(`\\$${mapName}:\\s*\\(([\\s\\S]*?)\\)`);
8
+ const match = data.match(mapRegex);
9
+ if (match) {
10
+ const mapContent = match[1];
11
+ const newMapContent = `$${mapName}: (${mapContent.replace(/\s*$/, '')},\n '${key}': ${value}\n)`;
12
+ data = data.replace(mapRegex, newMapContent);
13
+ } else {
14
+ throw new Error(`Error: Could not find ${mapName} map in the file`);
15
+ }
16
+ return data;
17
+ }
18
+
19
+ // Same edits as onboarding/themes.js, driven by resolved brand state (no prompts).
20
+ function start() {
21
+ const brand = brandModule.getBrand();
22
+ return new Promise((resolve, reject) => {
23
+ const themeVar = `
24
+ .theme-template-${brand.connectedBrandName} {
25
+ #logo {
26
+ display: inline-block;
27
+ width: 240px;
28
+ height: 120px;
29
+ background: url('${brand.logo}') no-repeat center center;
30
+ background-size: contain;
31
+ }
32
+ .navbar-toggle-container {
33
+ background: #222C38;
34
+
35
+ .icon-bar {
36
+ background: #fff !important;
37
+ }
38
+ }
39
+ .sidebar {
40
+ background: #222C38;
41
+ color: #ffffff;
42
+
43
+ ul a,
44
+ i {
45
+ color: #ffffff;
46
+ }
47
+
48
+ .brand-logo,
49
+ .brand-logo-text {
50
+ display: none;
51
+ }
52
+ .user-logged-in:after {
53
+ background: #26303C;
54
+ }
55
+ }
56
+ }
57
+ \n`;
58
+ fs.readFile(themesPath, 'utf8', (err, data) => {
59
+ if (err) {
60
+ return reject(err);
61
+ }
62
+ try {
63
+ data = addColorToMap(
64
+ 'theme-colors',
65
+ brand.connectedBrandName,
66
+ brand.color ? `$${brand.connectedBrandName}` : '$corp',
67
+ data
68
+ );
69
+ data = addColorToMap(
70
+ 'theme-secondary-colors',
71
+ brand.connectedBrandName,
72
+ brand.color ? `'${brand.connectedBrandName}'` : "'corp'",
73
+ data
74
+ );
75
+ } catch (mapError) {
76
+ return reject(mapError);
77
+ }
78
+ const insertionPoint = data.lastIndexOf('@each $color-name');
79
+ const newFileContent = data.slice(0, insertionPoint) + themeVar + data.slice(insertionPoint);
80
+ fs.writeFile(themesPath, newFileContent, 'utf8', (error) => {
81
+ if (error) {
82
+ reject(error);
83
+ } else {
84
+ console.log(chalk.cyan('themes.scss file updated successfully!'));
85
+ resolve(null);
86
+ }
87
+ });
88
+ });
89
+ });
90
+ }
91
+
92
+ module.exports = { start };
@@ -1,54 +1,53 @@
1
- const fs = require('fs');
2
- const brandModule = require('./brand.js');
3
- const chalk = require('chalk');
4
- const variablesColorsPath = 'src/ng1/assets/css/sass/variables-colors.scss';
5
- const defaultColor = '$corp';
6
-
7
- // Same edits as onboarding/variablesColors.js, driven by resolved brand state (no prompts).
8
- function start() {
9
- return new Promise((resolve, reject) => {
10
- try {
11
- const { connectedBrandName, color: hexColor } = brandModule.getBrand();
12
- let data = fs.readFileSync(variablesColorsPath, 'utf8');
13
-
14
- // Check if $colors map exists in the file
15
- const colorsRegex = /\$colors:\s*\(([\s\S]*?)\)/;
16
- const match = data.match(colorsRegex);
17
- if (match) {
18
- const colorsMap = match[1];
19
-
20
- // Append the new brand to the colors map
21
- const newColorsMap = `$colors: (${colorsMap},\n '${connectedBrandName}': ${
22
- hexColor ? '$' + connectedBrandName : defaultColor
23
- })`;
24
-
25
- // Replace the $colors map in the file with the new one
26
- data = data.replace(colorsRegex, newColorsMap);
27
- } else {
28
- return reject('Error: Could not find $colors map in the file');
29
- }
30
-
31
- const brandVar = `\$${connectedBrandName}: (
32
- 'lighten-5': lighten(${hexColor}, 25%),
33
- 'lighten-4': lighten(${hexColor}, 20%),
34
- 'lighten-3': lighten(${hexColor}, 15%),
35
- 'lighten-2': lighten(${hexColor}, 10%),
36
- 'lighten-1': lighten(${hexColor}, 5%),
37
- 'base': ${hexColor},
38
- 'darken-1': darken(${hexColor}, 5%),
39
- 'darken-2': darken(${hexColor}, 10%),
40
- 'darken-3': darken(${hexColor}, 15%),
41
- 'darken-4': darken(${hexColor}, 20%),
42
- );\n`;
43
-
44
- const newData = hexColor ? brandVar + data : data;
45
- fs.writeFileSync(variablesColorsPath, newData, 'utf8');
46
- console.log(chalk.cyan('\nvariables-colors.scss file updated successfully!'));
47
- resolve(null);
48
- } catch (error) {
49
- reject(error);
50
- }
51
- });
52
- }
53
-
54
- module.exports = { start };
1
+ const fs = require('fs');
2
+ const brandModule = require('./brand.js');
3
+ const chalk = require('chalk');
4
+ const variablesColorsPath = 'src/ng1/assets/css/sass/variables-colors.scss';
5
+ const defaultColor = '$corp';
6
+
7
+ // Same edits as onboarding/variablesColors.js, driven by resolved brand state (no prompts).
8
+ function start() {
9
+ return new Promise((resolve, reject) => {
10
+ try {
11
+ const { connectedBrandName, color: hexColor } = brandModule.getBrand();
12
+ let data = fs.readFileSync(variablesColorsPath, 'utf8');
13
+
14
+ // Check if $colors map exists in the file
15
+ const colorsRegex = /\$colors:\s*\(([\s\S]*?)\)/;
16
+ const match = data.match(colorsRegex);
17
+ if (match) {
18
+ const colorsMap = match[1];
19
+
20
+ const newColorsMap = `$colors: (${colorsMap.replace(/\s*$/, '')},\n '${connectedBrandName}': ${
21
+ hexColor ? '$' + connectedBrandName : defaultColor
22
+ }\n)`;
23
+
24
+ // Replace the $colors map in the file with the new one
25
+ data = data.replace(colorsRegex, newColorsMap);
26
+ } else {
27
+ return reject('Error: Could not find $colors map in the file');
28
+ }
29
+
30
+ const brandVar = `\$${connectedBrandName}: (
31
+ 'lighten-5': lighten(${hexColor}, 25%),
32
+ 'lighten-4': lighten(${hexColor}, 20%),
33
+ 'lighten-3': lighten(${hexColor}, 15%),
34
+ 'lighten-2': lighten(${hexColor}, 10%),
35
+ 'lighten-1': lighten(${hexColor}, 5%),
36
+ 'base': ${hexColor},
37
+ 'darken-1': darken(${hexColor}, 5%),
38
+ 'darken-2': darken(${hexColor}, 10%),
39
+ 'darken-3': darken(${hexColor}, 15%),
40
+ 'darken-4': darken(${hexColor}, 20%),
41
+ );\n`;
42
+
43
+ const newData = hexColor ? brandVar + data : data;
44
+ fs.writeFileSync(variablesColorsPath, newData, 'utf8');
45
+ console.log(chalk.cyan('\nvariables-colors.scss file updated successfully!'));
46
+ resolve(null);
47
+ } catch (error) {
48
+ reject(error);
49
+ }
50
+ });
51
+ }
52
+
53
+ module.exports = { start };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "antelope-cli",
3
- "version": "1.2.1",
3
+ "version": "1.2.3",
4
4
  "description": "CLI-Tool for automating processes for Antelope-Systems ",
5
5
  "main": "index.js",
6
6
  "bin": {