antelope-cli 1.2.1 → 1.2.2
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.
- package/ansible/onboarding-ca/README.md +41 -8
- package/ansible/onboarding-ca/onboard-ca.yml +22 -12
- package/ansible/onboarding-ca/requirements.yml +8 -0
- package/ansible/onboarding-ca/roles/onboard_ca/defaults/main.yml +29 -4
- package/ansible/onboarding-ca/roles/onboard_ca/tasks/main.yml +130 -13
- package/ansible/onboarding-ca/roles/onboard_ca/tasks/per_branch.yml +7 -6
- package/ansible/onboarding-ca/roles/onboard_ca/vars/main.yml +3 -0
- package/onboarding-crm/brand.js +22 -21
- package/onboarding-crm/brandLocations.js +33 -0
- package/onboarding-crm/createConfig.js +40 -38
- package/onboarding-crm/index.js +171 -157
- package/package.json +1 -1
|
@@ -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
|
-
-
|
|
22
|
-
|
|
23
|
-
|
|
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
|
|
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
69
|
| var | required | example | notes |
|
|
46
70
|
|-----|----------|---------|-------|
|
|
47
71
|
| `brand_name` | yes | `Zenith Horizon Group` | display name → PR title |
|
|
48
|
-
| `
|
|
49
|
-
| `
|
|
50
|
-
| `
|
|
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 |
|
|
51
77
|
| `brand_assets_folder` | no | `zenithhorizongroup` | folder name → CLI `--brand_assets_folder`, branch name; prompted separately. Blank ⇒ derived from `brand_name` (lowercased, spaces removed) |
|
|
52
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 |
|
|
53
82
|
| `antelope_cli_version` | no | `1.2.0` | pinned published CLI version |
|
|
54
83
|
| `work_dir` | no | `/tmp/onboard-ca/web-client-area` | clone location |
|
|
55
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
|
-
#
|
|
9
|
-
#
|
|
10
|
-
#
|
|
11
|
-
#
|
|
12
|
-
#
|
|
13
|
-
#
|
|
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
|
|
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:
|
|
38
|
+
- name: jira_key_client_branding
|
|
31
39
|
prompt: "Jira key (e.g. ATLP-47067)"
|
|
32
40
|
private: false
|
|
33
|
-
- name:
|
|
34
|
-
prompt: "
|
|
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: "
|
|
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"
|
|
@@ -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: "
|
|
20
|
-
git_user_email: "
|
|
19
|
+
git_user_name: "Jenkins"
|
|
20
|
+
git_user_email: "jenkins@xsites.co.il"
|
|
21
21
|
|
|
22
|
-
#
|
|
23
|
-
#
|
|
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
|
-
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
30
|
-
|
|
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
|
-
|
|
38
|
-
|
|
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:
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
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 }}/{{
|
|
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 "{{
|
|
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
|
-
|
|
81
|
-
|
|
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: "{{
|
|
85
|
-
description: "{{
|
|
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"
|
package/onboarding-crm/brand.js
CHANGED
|
@@ -1,21 +1,22 @@
|
|
|
1
|
-
let brand = {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
connectedBrandName: '',
|
|
5
|
-
color: '',
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
FCMSenderId: '',
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
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
|
-
//
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
|
|
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.connectedBrandName}',
|
|
33
|
+
favicon: '${brand.favicon}',
|
|
34
|
+
affiliatesApiUrl: '',
|
|
35
|
+
FCMSenderId: '${brand.FCMSenderId}',
|
|
36
|
+
brandAssetsFolder: '${brand.connectedBrandName}',
|
|
37
|
+
};`;
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
module.exports = { start };
|
package/onboarding-crm/index.js
CHANGED
|
@@ -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
|
-
|
|
10
|
-
|
|
11
|
-
const
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
src/assets/
|
|
19
|
-
src/
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
--
|
|
26
|
-
--
|
|
27
|
-
--
|
|
28
|
-
--
|
|
29
|
-
--
|
|
30
|
-
--
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
'--
|
|
41
|
-
'--
|
|
42
|
-
'--
|
|
43
|
-
'--
|
|
44
|
-
'--
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
if (opts
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
const
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
const
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
const
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
if (!
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
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();
|