@webship.co/workspace-odoo 0.0.0-stage → 1.0.8

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.
@@ -0,0 +1,30 @@
1
+ Feature: The Odoo plugin on the dashboard
2
+ Once the plugin is activated and installed, the sites folders offer the Odoo builder, each
3
+ AI folder lists the items of its kind the plugin ships, and the Plugins page lists the plugin
4
+ with its steps.
5
+
6
+ Scenario: The plugin is listed on the Plugins page
7
+ Given I am on "/settings/plugins"
8
+ Then I should see "Odoo"
9
+ And I should see "@webship.co/workspace-odoo"
10
+
11
+ Scenario: A sites folder offers the Odoo builder
12
+ Given I am on "/dev"
13
+ Then "option[value='cmd-odoo-project.sh']" should be attached
14
+
15
+ Scenario: The AI Agents folder lists the agents the plugin ships
16
+ Given I am on "/ai-agents"
17
+ Then I should see "odoo-site-builder"
18
+ And I should see "odoo-tester"
19
+
20
+ Scenario: The AI Skills folder lists the skill the plugin ships
21
+ Given I am on "/ai-skills"
22
+ Then I should see "odoo-scenarios"
23
+
24
+ Scenario: The AI Commands folder lists the command the plugin ships
25
+ Given I am on "/ai-commands"
26
+ Then I should see "odoo-test"
27
+
28
+ Scenario: The AI Prompts folder lists the prompt the plugin ships
29
+ Given I am on "/ai-prompts"
30
+ Then I should see "odoo-build-and-test"
package/package.json CHANGED
@@ -1,6 +1,98 @@
1
1
  {
2
2
  "name": "@webship.co/workspace-odoo",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "1.0.8",
4
+ "description": "Odoo Community for Webship Workspace: the builder (`cmd-odoo-project.sh`) and the odoo-bin tool in every sites folder, the default accounts, the Odoo step pack wired into the testing setup, and its AI items in their folders: two agents, a skill, a command and a prompt.",
5
+ "main": "dist/src/index.js",
6
+ "types": "dist/src/index.d.ts",
7
+ "license": "ISC",
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "https://github.com/webship/workspace-odoo.git"
11
+ },
12
+ "files": [
13
+ "dist/src",
14
+ "ai",
15
+ "commands",
16
+ "docs",
17
+ "features",
18
+ "scripts",
19
+ "testing",
20
+ "README.md"
21
+ ],
22
+ "scripts": {
23
+ "build": "tsc -p tsconfig.json",
24
+ "prepare": "npm run build",
25
+ "test": "npm run build && node --test dist/tests/*.test.js",
26
+ "lint": "tsc -p tsconfig.json --noEmit"
27
+ },
28
+ "workspacePlugin": {
29
+ "name": "odoo",
30
+ "label": "Odoo",
31
+ "needs": [
32
+ [
33
+ "dev",
34
+ "test",
35
+ "sandboxes",
36
+ "demos"
37
+ ],
38
+ "ai-agents",
39
+ "ai-skills",
40
+ "ai-commands",
41
+ "ai-prompts"
42
+ ],
43
+ "commands": {
44
+ "types": [
45
+ "sites"
46
+ ],
47
+ "workspaces": [
48
+ "dev",
49
+ "test",
50
+ "sandboxes",
51
+ "demos"
52
+ ],
53
+ "files": [
54
+ "commands/cmd-odoo-project.sh",
55
+ "commands/cmd-tools-odoo-cli.sh"
56
+ ]
57
+ },
58
+ "scripts": {
59
+ "functions": [
60
+ "scripts/functions/fun-odoo.sh"
61
+ ],
62
+ "args": [
63
+ "scripts/args/arg-odoo.sh",
64
+ "scripts/args/arg-odoo-cli.sh"
65
+ ],
66
+ "machine": [
67
+ "scripts/odoo-accounts.py",
68
+ "testing/site.feature"
69
+ ]
70
+ },
71
+ "ai": {
72
+ "agents": [
73
+ "ai/agents/odoo-site-builder.md",
74
+ "ai/agents/odoo-tester.md"
75
+ ],
76
+ "skills": [
77
+ "ai/skills/odoo-scenarios"
78
+ ],
79
+ "commands": [
80
+ "ai/commands/odoo-test.md"
81
+ ],
82
+ "prompts": [
83
+ "ai/prompts/odoo-build-and-test.md"
84
+ ]
85
+ },
86
+ "features": [
87
+ "features/odoo.feature"
88
+ ]
89
+ },
90
+ "devDependencies": {
91
+ "@types/node": "^24.0.0",
92
+ "@webship.co/workspace-plugin-api": "^1.0.7",
93
+ "typescript": "^5.9.0"
94
+ },
95
+ "publishConfig": {
96
+ "access": "public"
97
+ }
98
+ }
@@ -0,0 +1,28 @@
1
+ #!/usr/bin/env bash
2
+
3
+ argparse "$@" <<ARGEOF || exit 1
4
+ parser.add_argument('PROJECT_NAME',
5
+ help='The Odoo project in this workspace.')
6
+ parser.add_argument('-r', '--run',
7
+ default="_none_",
8
+ help='Arguments for odoo-bin, run on the database of the project without the web server. Example: --run "-u sale --stop-after-init". A single argument that starts with a dash is given with an equals sign: --run=--version')
9
+ parser.add_argument('-i', '--install-apps',
10
+ default="_none_",
11
+ help='Install apps by their technical names, with commas, then start Odoo again. Example: --install-apps project,hr')
12
+ parser.add_argument('-u', '--update',
13
+ action='store_true',
14
+ default=False,
15
+ help='Update every installed module (odoo-bin -u all), then start Odoo again.')
16
+ parser.add_argument('-s', '--restart',
17
+ action='store_true',
18
+ default=False,
19
+ help='Start Odoo again, and wait until it answers.')
20
+ parser.add_argument('-a', '--add-users',
21
+ action='store_true',
22
+ default=False,
23
+ help='Add the default accounts. One that is already there gets its groups and its password again.')
24
+ parser.add_argument('-c', '--cancel-users',
25
+ action='store_true',
26
+ default=False,
27
+ help='Delete the default accounts; one that is part of the data of the site, or that Odoo will not delete, is archived.')
28
+ ARGEOF
@@ -0,0 +1,28 @@
1
+ #!/usr/bin/env bash
2
+
3
+ argparse "$@" <<ARGEOF || exit 1
4
+ parser.add_argument('PROJECT_NAME',
5
+ help='The name of the project: its folder, its DDEV project and its host name.')
6
+ parser.add_argument('-v', '--version',
7
+ default="${site_version}",
8
+ help='The Odoo Community release to run, as its container image is tagged. The apps, the accounts and the tests of this builder are proven on the default. [default %(default)s]')
9
+ parser.add_argument('-i', '--install',
10
+ action='store_true',
11
+ default=False,
12
+ help='Create the database with the apps, with the workspace account as the administrator.')
13
+ parser.add_argument('-a', '--add-users',
14
+ action='store_true',
15
+ default=False,
16
+ help='With --install, add the default accounts: the ones the test scenarios log in as.')
17
+ parser.add_argument('--apps',
18
+ default="contacts,crm,sale_management,account,website,website_blog,website_sale,website_event,website_hr_recruitment",
19
+ help='The apps --install installs, by their technical names, with commas. Example: --apps contacts,website [default %(default)s]')
20
+ parser.add_argument('--without-demo',
21
+ action='store_true',
22
+ default=False,
23
+ help='With --install, leave the demo data of Odoo out.')
24
+ parser.add_argument('-l', '--launch',
25
+ action='store_true',
26
+ default=False,
27
+ help='Open the site in a browser when it is ready.')
28
+ ARGEOF
@@ -0,0 +1,435 @@
1
+ #!/usr/bin/env bash
2
+
3
+ # Odoo Community for the Webship Workspace: build a site, run odoo-bin on it, add its default
4
+ # accounts, and wire its step pack into the testing setup.
5
+ #
6
+ # Placed by `plugins.sh --install odoo` in core/scripts/plugins/odoo/functions/, where bootstrap
7
+ # sources it after the core's own functions. Everything runs in the project's containers (DDEV by
8
+ # default), never on the host: Odoo is the official Community image as a service of the project,
9
+ # the router serves its port at the project's address (Odoo runs with --proxy-mode and a
10
+ # --db-filter for its one database), and PostgreSQL is the container tool's own database, so the
11
+ # database commands and the backups work as for any other project. Community only: no Enterprise
12
+ # edition, no vendor account, and the module that talks to an outside service is uninstalled.
13
+ #
14
+ # The core carries an example Odoo builder of its own (functions/fun-odoo.sh: build_odoo,
15
+ # odoo_cli, odoo_write_compose, odoo_write_nginx, behind cmd-odoo20-project.sh). This file defines
16
+ # none of those names, so the example keeps working with the plugin installed.
17
+ #
18
+ # DDEV has no project type for Odoo, so an Odoo project of this plugin is recognised by a file
19
+ # (.ddev/config.odoo.yaml, written by the builder), and four names are the ones the core's tools
20
+ # look for:
21
+ # set_odoo_users the default accounts (cmd-tools-add-users.sh <project> odoo)
22
+ # add_odoo_users create them in the Odoo shell (the same tool, and --add-users of the builder)
23
+ # cancel_odoo_users delete or archive them (cmd-tools-cancel-users.sh <project> odoo)
24
+ # testing_configure_odoo the step pack in the testing stack (cmd-tools-testing.sh <project>)
25
+
26
+ # The step pack an Odoo project is tested with, and the release line it is required at.
27
+ readonly ODOO_STEPS_PACKAGE='@webship-js/odoo-steps';
28
+ readonly ODOO_STEPS_VERSION='^2.1.0';
29
+
30
+ # The one database of a project (the container tool's own), and the port Odoo listens on.
31
+ readonly ODOO_DATABASE='db';
32
+ readonly ODOO_PORT='8069';
33
+
34
+ # The default accounts, the ones a scenario logs in as: one per kind of user. Accounts are
35
+ # configuration, created once on the site; a test suite never creates or deletes one. The
36
+ # administrator Odoo creates is the workspace account and is not in this list.
37
+ function set_odoo_users() {
38
+ users=(administrator salesperson accountant employee portal);
39
+ local user ;
40
+ for user in "${users[@]}" ; do
41
+ printf -v "user_${user}_name" '%s' "${user}" ;
42
+ printf -v "user_${user}_mail" '%s' "${user}@example.com" ;
43
+ printf -v "user_${user}_password" '%s' 'dD.123123ddd' ;
44
+ printf -v "user_${user}_role" '%s' "${user}" ;
45
+ done
46
+ }
47
+
48
+ # Is this folder an Odoo project of this plugin? The builder leaves .ddev/config.odoo.yaml in it.
49
+ function odoo_is_project() {
50
+ local project_path="$1";
51
+ [ -f "${project_path}/.ddev/config.odoo.yaml" ] ;
52
+ }
53
+
54
+ # Go to the project a tool was asked about, or say why not.
55
+ function odoo_enter_project() {
56
+ local project_path="${WORKSPACE_ROOT}/${doc_name}/${PROJECT_NAME}";
57
+ if [ ! -d "${project_path}" ] ; then
58
+ echo "No project ${PROJECT_NAME} in ${WORKSPACE_ROOT}/${doc_name}." ;
59
+ return 1 ;
60
+ fi
61
+ if ! odoo_is_project "${project_path}" ; then
62
+ echo "${doc_name}/${PROJECT_NAME} is not an Odoo project of this plugin (it has no .ddev/config.odoo.yaml)." ;
63
+ return 1 ;
64
+ fi
65
+ cd "${project_path}" || return 1 ;
66
+ }
67
+
68
+ # What the container tool is told about Odoo: the official Community image as a service of the
69
+ # project, on the project's database, with the router serving its port at the project's address.
70
+ function odoo_site_write_ddev_config() {
71
+ local project_path="$1" version="$2";
72
+ mkdir -p "${project_path}/.ddev" "${project_path}/addons" || return 1 ;
73
+ cat > "${project_path}/.ddev/config.odoo.yaml" <<YAMLEOF
74
+ # Odoo Community in this project. Written by the Odoo builder of the workspace; build again rather
75
+ # than editing by hand. The release is the tag of the image in docker-compose.odoo.yaml.
76
+ web_environment:
77
+ - ODOO_VERSION=${version}
78
+ YAMLEOF
79
+ cat > "${project_path}/.ddev/docker-compose.odoo.yaml" <<COMPOSEEOF
80
+ ## Odoo Community, as a service of this project. Written by the Odoo builder of the workspace;
81
+ ## build again rather than editing by hand. Custom modules go in the project's addons/ folder.
82
+ volumes:
83
+ odoo_data:
84
+
85
+ services:
86
+ odoo:
87
+ container_name: ddev-\${DDEV_SITENAME}-odoo
88
+ image: "odoo:${version}"
89
+ restart: "no"
90
+ labels:
91
+ com.ddev.site-name: \${DDEV_SITENAME}
92
+ com.ddev.approot: \${DDEV_APPROOT}
93
+ depends_on:
94
+ - db
95
+ environment:
96
+ # The image's entrypoint turns these three into the database options: the container tool's
97
+ # own database.
98
+ - HOST=db
99
+ - USER=db
100
+ - PASSWORD=db
101
+ # The router serves Odoo's port at the project's address.
102
+ - VIRTUAL_HOST=\$DDEV_HOSTNAME
103
+ - HTTP_EXPOSE=80:${ODOO_PORT}
104
+ - HTTPS_EXPOSE=443:${ODOO_PORT}
105
+ expose:
106
+ - "${ODOO_PORT}"
107
+ # --proxy-mode: Odoo sits behind the router and reads the scheme and the host from the
108
+ # forwarded headers, so its links are https.
109
+ # --db-filter: one database only, so a visitor is never sent to the database selector.
110
+ # No --workers: in its default threaded mode Odoo serves the pages and the websocket of the
111
+ # bus on the same port, which is the one the router exposes.
112
+ command: odoo --proxy-mode --db-filter=^${ODOO_DATABASE}\$\$ --database=${ODOO_DATABASE} --max-cron-threads=1
113
+ volumes:
114
+ - odoo_data:/var/lib/odoo
115
+ - ../addons:/mnt/extra-addons
116
+ COMPOSEEOF
117
+ }
118
+
119
+ # odoo-bin in the Odoo container, on the database of the project, without the web server.
120
+ function odoo_bin() {
121
+ ddev exec -s odoo odoo --db_host=db --db_user=db --db_password=db -d "${ODOO_DATABASE}" --no-http "$@" ;
122
+ }
123
+
124
+ # The release of Odoo the project runs.
125
+ function odoo_version() {
126
+ ddev exec -s odoo odoo --version 2>/dev/null | tr -d '\r' | sed 's/^Odoo Server //' ;
127
+ }
128
+
129
+ # Has the database been created with Odoo's base module?
130
+ function odoo_is_installed() {
131
+ [ "$(ddev exec -s db "psql -U db -d ${ODOO_DATABASE} -tAc \"select state from ir_module_module where name = 'base'\"" 2>/dev/null | tr -d '\r[:space:]')" == 'installed' ] ;
132
+ }
133
+
134
+ # Wait until Odoo answers on its own port.
135
+ function odoo_site_wait() {
136
+ local attempt code ;
137
+ for attempt in $(seq 1 90) ; do
138
+ code="$(ddev exec "curl -s -o /dev/null -w '%{http_code}' http://odoo:${ODOO_PORT}/web/login" 2>/dev/null | tr -d '\r')" ;
139
+ case "${code}" in
140
+ 200|303) return 0 ;;
141
+ esac
142
+ sleep 2 ;
143
+ done
144
+ echo "Odoo did not answer; the end of its log:" ;
145
+ ddev logs -s odoo 2>/dev/null | tail -n 40 ;
146
+ return 1 ;
147
+ }
148
+
149
+ # Start Odoo again, and wait for it. The running server loaded its code before the last change
150
+ # to the modules, so it starts again on the database as it is now.
151
+ function odoo_site_restart() {
152
+ docker restart "ddev-${PROJECT_NAME}-odoo" > /dev/null || return 1 ;
153
+ odoo_site_wait ;
154
+ }
155
+
156
+ # Run verbs of odoo-accounts.py (local, owner, add, cancel, list, apps; with commas) in the Odoo
157
+ # shell of the project. The script is piped to the shell, and what it needs travels as base64 of
158
+ # a JSON object, so no password is written into a file of the project and no quoting can break a
159
+ # value.
160
+ function odoo_accounts() {
161
+ local verbs="$1" script="${WORKSPACE_SCRIPTS}/plugins/odoo/odoo-accounts.py" rows="" user name mail password role payload ;
162
+ if [ ! -f "${script}" ] ; then
163
+ echo "odoo-accounts.py is not in ${WORKSPACE_SCRIPTS}/plugins/odoo: install the plugin (plugins.sh --install odoo)." ;
164
+ return 1 ;
165
+ fi
166
+ for user in "${users[@]}" ; do
167
+ name="user_${user}_name"; mail="user_${user}_mail"; password="user_${user}_password"; role="user_${user}_role";
168
+ rows="${rows}${!name}"$'\x1f'"${!mail}"$'\x1f'"${!password}"$'\x1f'"${!role}"$'\x1e' ;
169
+ done
170
+ payload="$(ODOO_OWNER_NAME="${account_name}" ODOO_OWNER_MAIL="${account_mail}" ODOO_OWNER_PASS="${account_pass}" \
171
+ ODOO_ROWS="${rows}" python3 -c '
172
+ import base64, json, os
173
+ accounts = [dict(zip(("name", "email", "password", "role"), row.split("\x1f"))) for row in os.environ["ODOO_ROWS"].split("\x1e") if row]
174
+ print(base64.b64encode(json.dumps({
175
+ "owner": {"name": os.environ["ODOO_OWNER_NAME"], "email": os.environ["ODOO_OWNER_MAIL"], "password": os.environ["ODOO_OWNER_PASS"]},
176
+ "accounts": accounts,
177
+ }).encode()).decode())')" || return 1 ;
178
+ ddev exec -s odoo "ODOO_ACCOUNTS=${payload} ODOO_ACCOUNTS_VERB=${verbs} odoo shell --db_host=db --db_user=db --db_password=db -d ${ODOO_DATABASE} --no-http --log-level=warn" < "${script}" ;
179
+ }
180
+
181
+ # Build an Odoo Community site from nothing: the containers, and with --install the database with
182
+ # its apps and its administrator.
183
+ function odoo_build() {
184
+ trim_project_name ;
185
+ local base_url="https://${PROJECT_NAME}.ddev.site" ;
186
+ local version="${VERSION:-${site_version:-17.0}}" ;
187
+ local project_path="${WORKSPACE_ROOT}/${doc_name}/${PROJECT_NAME}" ;
188
+ local apps="${APPS:-contacts}" ;
189
+
190
+ cd ${WORKSPACE_ROOT}/${doc_name} || exit 1 ;
191
+
192
+ drop_database ;
193
+ rm -rf "${PROJECT_NAME}" ;
194
+ mkdir -p "${PROJECT_NAME}" ;
195
+ cd "${project_path}" || exit 1 ;
196
+
197
+ # A generic web server: the web container serves nothing, the router goes to Odoo. The database
198
+ # is the container tool's PostgreSQL, and the project keeps the Node.js of the workspace for its
199
+ # tests.
200
+ ddev config --project-type=generic --webserver-type=generic --docroot="" --project-name=${PROJECT_NAME} \
201
+ --database=postgres:16 --nodejs-version=$(ddev_nodejs_version) || exit 1 ;
202
+ odoo_site_write_ddev_config "${project_path}" "${version}" || exit 1 ;
203
+
204
+ echo "Starting Odoo ${version} Community. The first run pulls the image, which takes a few minutes." ;
205
+ ddev_up start -y || exit 1 ;
206
+
207
+ if [ "${INSTALL}" == 'yes' ] ; then
208
+ local demo="" ;
209
+ [ "${WITHOUT_DEMO}" == 'yes' ] && demo="--without-demo=all" ;
210
+ echo "Creating the ${ODOO_DATABASE} database with the apps${demo:+ (without demo data)}: ${apps}" ;
211
+ odoo_bin --log-level=warn --stop-after-init -i "base,${apps}" ${demo} || { echo "The Odoo install failed." ; exit 1 ; } ;
212
+
213
+ # Keep the site local, and give it the workspace account as its administrator.
214
+ users=() ;
215
+ local verbs="local,owner" ;
216
+ if [ "${ADD_USERS}" == 'yes' ] ; then
217
+ set_odoo_users ;
218
+ verbs="local,owner,add" ;
219
+ fi
220
+ odoo_accounts "${verbs}" || { echo "The accounts of the site could not be set." ; exit 1 ; } ;
221
+ odoo_site_restart || exit 1 ;
222
+
223
+ echo "${doc_name} ${PROJECT_NAME} has been installed: Odoo $(odoo_version) Community.";
224
+ echo "Go to ${base_url}/web/login and log in as ${account_name}";
225
+ else
226
+ [ "${ADD_USERS}" == 'yes' ] && echo "--add-users needs --install: there is no database to add accounts to yet." ;
227
+ echo "${doc_name} ${PROJECT_NAME} is running with an empty database: Odoo $(odoo_version) Community.";
228
+ echo "Create the database with: bash cmd-tools-odoo-cli.sh ${PROJECT_NAME} --install-apps contacts (or build with --install)";
229
+ fi
230
+
231
+ if [ "${LAUNCH}" == 'yes' ] ; then
232
+ # The address is given: the router serves Odoo, so the web container has none of its own.
233
+ ddev launch "${base_url}/web/login" ;
234
+ fi
235
+
236
+ cd ${WORKSPACE_ROOT}/${doc_name};
237
+ }
238
+
239
+ # Create the default accounts in the Odoo shell, each with the groups of its role. One that is
240
+ # already there gets its groups and its password again. The list is loaded by the caller
241
+ # (set_odoo_users): the core's add_users, the builder, the CLI tool.
242
+ function add_odoo_users() {
243
+ odoo_enter_project || return 1 ;
244
+ local user user_name user_mail user_password user_role ;
245
+ for user in "${users[@]}" ; do
246
+ user_name="user_${user}_name"; user_mail="user_${user}_mail";
247
+ user_password="user_${user}_password"; user_role="user_${user}_role";
248
+
249
+ echo " ---------------------------------------------------------------- ";
250
+ echo " User name: ${!user_name}";
251
+ echo " User mail: ${!user_mail}";
252
+ echo " User password: ${!user_password}";
253
+ echo " User role: ${!user_role}";
254
+ echo " ================================================================= ";
255
+ done
256
+ odoo_accounts add ;
257
+ local status=$? ;
258
+ cd ${WORKSPACE_ROOT}/${doc_name};
259
+ return ${status} ;
260
+ }
261
+
262
+ # Delete the default accounts. One that Odoo will not delete is archived instead.
263
+ function cancel_odoo_users() {
264
+ odoo_enter_project || return 1 ;
265
+ local user user_name ;
266
+ for user in "${users[@]}" ; do
267
+ user_name="user_${user}_name";
268
+ echo " ---------------------------------------------------------------- ";
269
+ echo " User name: ${!user_name}";
270
+ echo " ================================================================= ";
271
+ done
272
+ odoo_accounts cancel ;
273
+ local status=$? ;
274
+ cd ${WORKSPACE_ROOT}/${doc_name};
275
+ return ${status} ;
276
+ }
277
+
278
+ # What cmd-tools-odoo-cli.sh does: the first of --run, --install-apps, --update, --restart,
279
+ # --add-users, --cancel-users that was asked for; with none of them, what the site is.
280
+ function odoo_cli_tool() {
281
+ trim_project_name ;
282
+ odoo_enter_project || exit 1 ;
283
+
284
+ if ! ddev describe -j 2>/dev/null | grep -q '"status":"running"' ; then
285
+ ddev start -y || exit 1 ;
286
+ fi
287
+
288
+ if [ ! "${RUN}" == '_none_' ] ; then
289
+ # Unquoted on purpose: --run "-u sale --stop-after-init" is a list of arguments.
290
+ odoo_bin ${RUN} ;
291
+ exit $? ;
292
+ fi
293
+
294
+ if [ ! "${INSTALL_APPS}" == '_none_' ] ; then
295
+ local created='no' ;
296
+ odoo_is_installed || created='yes' ;
297
+ odoo_bin --log-level=warn --stop-after-init -i "${INSTALL_APPS}" || exit 1 ;
298
+ users=() ;
299
+ if [ "${created}" == 'yes' ] ; then
300
+ # The database was empty: this created it, so it gets its administrator too.
301
+ odoo_accounts local,owner || exit 1 ;
302
+ else
303
+ odoo_accounts local || exit 1 ;
304
+ fi
305
+ odoo_site_restart || exit 1 ;
306
+ elif [ "${UPDATE}" == 'yes' ] ; then
307
+ odoo_bin --log-level=warn --stop-after-init -u all || exit 1 ;
308
+ odoo_site_restart || exit 1 ;
309
+ echo "Odoo $(odoo_version): every installed module is updated." ;
310
+ elif [ "${RESTART}" == 'yes' ] ; then
311
+ odoo_site_restart || exit 1 ;
312
+ echo "Odoo $(odoo_version) is running." ;
313
+ elif [ "${ADD_USERS}" == 'yes' ] ; then
314
+ set_odoo_users ;
315
+ add_odoo_users ;
316
+ elif [ "${CANCEL_USERS}" == 'yes' ] ; then
317
+ set_odoo_users ;
318
+ cancel_odoo_users ;
319
+ else
320
+ echo "${doc_name}/${PROJECT_NAME}: Odoo $(odoo_version) Community at https://${PROJECT_NAME}.ddev.site" ;
321
+ if odoo_is_installed ; then
322
+ users=() ;
323
+ echo "" ; echo "Apps:" ; odoo_accounts apps ;
324
+ echo "" ; echo "Accounts:" ; odoo_accounts list ;
325
+ else
326
+ echo " The database is empty: create it with --install-apps <apps>, or build with --install." ;
327
+ fi
328
+ fi
329
+ cd ${WORKSPACE_ROOT}/${doc_name};
330
+ }
331
+
332
+ # The step pack in the testing stack of an Odoo project. The core's testing_configure calls this
333
+ # in the project folder once webship-js is scaffolded and before the dependencies are installed;
334
+ # for a project that is not this plugin's it changes nothing.
335
+ #
336
+ # It adds the pack to package.json, lists its step definitions in cucumber.js after webship-js's
337
+ # own, gives cucumber.js the users registry of the default accounts (a handle a scenario reads,
338
+ # mapped to an account; never the workspace account) and the address of the site, and writes a
339
+ # first feature when the project has none of its own. Every part is skipped when it is already there, so a second run adds what
340
+ # the first missed and never resets a suite.
341
+ function testing_configure_odoo() {
342
+ local project_path="$1";
343
+ odoo_is_project "${project_path}" || return 0 ;
344
+ if [ ! -f "${project_path}/cucumber.js" ] || [ ! -f "${project_path}/package.json" ] ; then
345
+ echo " Odoo: cucumber.js is not here yet — run the testing setup again once webship-js is scaffolded." ;
346
+ return 1 ;
347
+ fi
348
+
349
+ echo "Adding the Odoo step pack (${ODOO_STEPS_PACKAGE} ${ODOO_STEPS_VERSION}).";
350
+ set_odoo_users ;
351
+ local registry="" user user_name user_password handle ;
352
+ for user in "${users[@]}" ; do
353
+ user_name="user_${user}_name"; user_password="user_${user}_password";
354
+ # The pack's features call the administrator "admin".
355
+ handle="${user}" ; [ "${user}" == 'administrator' ] && handle="admin" ;
356
+ registry="${registry}${handle}|${!user_name}|${!user_password}"$'\n' ;
357
+ done
358
+
359
+ # The address of the site. The router serves Odoo, not the web container, so the container tool
360
+ # has no address to hand the web container (LAUNCH_URL is empty there): the suite gets it here.
361
+ local site_name ;
362
+ site_name="$(sed -n 's/^name:[[:space:]]*"\{0,1\}\([A-Za-z0-9._-]*\)"\{0,1\}[[:space:]]*$/\1/p' "${project_path}/.ddev/config.yaml" | head -n 1)" ;
363
+
364
+ python3 - "${project_path}" "${ODOO_STEPS_PACKAGE}" "${ODOO_STEPS_VERSION}" 'ODOO' "${registry}" "https://${site_name:-${PROJECT_NAME}}.ddev.site" <<'PYEOF' || return 1
365
+ import json, re, sys
366
+
367
+ project, pack, version, prefix, registry, address = sys.argv[1:7]
368
+
369
+ # package.json: the pack as a dev dependency, at the release line this plugin was proven on.
370
+ pkg_file = project + '/package.json'
371
+ pkg = json.load(open(pkg_file))
372
+ if pack not in pkg.get('dependencies', {}) and pack not in pkg.get('devDependencies', {}):
373
+ pkg.setdefault('devDependencies', {})[pack] = version
374
+ open(pkg_file, 'w').write(json.dumps(pkg, indent=2) + '\n')
375
+ print(' package.json: %s %s' % (pack, version))
376
+
377
+ # cucumber.js: the pack's step definitions after webship-js's own, and the users registry.
378
+ cucumber_file = project + '/cucumber.js'
379
+ src = open(cucumber_file).read()
380
+ before = src
381
+
382
+ if ('node_modules/%s/' % pack) not in src:
383
+ line = "'node_modules/%s/tests/step-definitions/**/*.js'," % pack
384
+ core = re.search(r"^([ \t]*)'node_modules/webship-js/tests/step-definitions/[^']*',.*$", src, re.M)
385
+ opening = re.search(r"^([ \t]*)require:[ \t]*\[[ \t]*$", src, re.M)
386
+ if core:
387
+ src = src[:core.end()] + '\n' + core.group(1) + line + src[core.end():]
388
+ elif opening:
389
+ src = src[:opening.end()] + '\n' + opening.group(1) + ' ' + line + src[opening.end():]
390
+ else:
391
+ sys.exit(" cucumber.js has no require list this can add to. Add this line to it:\n " + line)
392
+ print(' cucumber.js: the step definitions of %s' % pack)
393
+
394
+ if not re.search(r"^[ \t]*users:[ \t]*\{", src, re.M):
395
+ world = re.search(r"^([ \t]*)worldParameters:[ \t]*\{[ \t]*$", src, re.M)
396
+ if not world:
397
+ sys.exit(" cucumber.js has no worldParameters block to add the users registry to.")
398
+ pad = world.group(1) + ' '
399
+ rows = [r.split('|') for r in registry.splitlines() if r.strip()]
400
+ block = [
401
+ pad + "// The users registry: a handle a scenario reads, mapped to an account of this site (the default",
402
+ pad + "// accounts: cmd-tools-add-users.sh). Override one with %s_<HANDLE>_USER and %s_<HANDLE>_PASSWORD." % (prefix, prefix),
403
+ pad + "users: {",
404
+ ]
405
+ for handle, name, password in rows:
406
+ env = '%s_%s' % (prefix, handle.upper())
407
+ block.append(pad + " %s: { username: process.env.%s_USER || '%s', password: process.env.%s_PASSWORD || '%s' }," % (handle, env, name, env, password))
408
+ block.append(pad + "},")
409
+ src = src[:world.end()] + '\n' + '\n'.join(block) + src[world.end():]
410
+ print(' cucumber.js: the users registry (%s)' % ', '.join(r[0] for r in rows))
411
+
412
+ # The address the suite opens when LAUNCH_URL is not set, which it is not in this project: the
413
+ # placeholder of the scaffold becomes the address of the site. An address somebody set stays.
414
+ placeholder = "process.env.LAUNCH_URL || 'http://localhost:8080'"
415
+ if placeholder in src:
416
+ src = src.replace(placeholder, "process.env.LAUNCH_URL || '%s'" % address)
417
+ print(' cucumber.js: the address of the site (%s)' % address)
418
+
419
+ if src != before:
420
+ open(cucumber_file, 'w').write(src)
421
+ PYEOF
422
+
423
+ # A first feature, when the project has none for the site yet: the front page, the login page,
424
+ # each default account in its role.
425
+ local feature="${WORKSPACE_SCRIPTS}/plugins/odoo/site.feature" ;
426
+ if [ -f "${feature}" ] && [ ! -f "${project_path}/tests/features/site.feature" ] ; then
427
+ mkdir -p "${project_path}/tests/features" && cp "${feature}" "${project_path}/tests/features/site.feature" \
428
+ && echo " tests/features/site.feature: a first feature for the site" ;
429
+ fi
430
+
431
+ # The scenarios log in as the default accounts: make sure a site that has its database has them.
432
+ if odoo_is_installed ; then
433
+ ( add_odoo_users > /dev/null ) && echo " the default accounts are on the site (${users[*]})" ;
434
+ fi
435
+ }