Skip to content

Commit d42cb5b

Browse files
authored
Merge pull request #226 from Jurj-Bogdan/laminas-ecosystem-issue199
preview implementation of the Laminas Integration RFC
2 parents d864137 + 2403420 commit d42cb5b

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

45 files changed

+2834
-69
lines changed
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
name: Validate integration packages JSON file
2+
3+
on:
4+
pull_request:
5+
push:
6+
branches:
7+
tags:
8+
9+
jobs:
10+
validate-packages:
11+
runs-on: ubuntu-latest
12+
steps:
13+
- name: Checkout
14+
uses: actions/checkout@v4
15+
16+
- name: schema-validation-action
17+
uses: cardinalby/schema-validator-action@3.1.1
18+
with:
19+
file: "data/integration/integration-packages.json"
20+
schema: "data/integration/integration-packages-schema.json"

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,5 +9,6 @@ phpunit.xml
99
public/css/*
1010
public/js/*
1111
public/share/*
12+
public/images/packages/*
1213
var/
1314
vendor/

.platform.app.yaml

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,12 @@ mounts:
2222
'data/cache':
2323
source: local
2424
source_path: data/cache
25+
'data/integration/database':
26+
source: local
27+
source_path: data/integration/database
28+
'public/images/packages':
29+
source: local
30+
source_path: public/images/packages
2531
'public/share':
2632
source: local
2733
source_path: public/share
@@ -54,6 +60,7 @@ hooks:
5460
rm -f data/cache/config-cache.php
5561
if [ ! -e data/cache/releases.rss ];then cp templates/releases.rss data/cache/ ;fi
5662
./vendor/bin/laminas repository:generate-data "$PLATFORM_VARIABLES" | base64 --decode | jq '."REPO_TOKEN"'
63+
./vendor/bin/laminas integration:create-db
5764
5865
crons:
5966
snapshot:
@@ -76,9 +83,24 @@ crons:
7683
commands :
7784
start: |
7885
if [ "$PLATFORM_BRANCH" = master ]; then
79-
./vendor/bin/laminas repository:generate-data "$PLATFORM_VARIABLES" | base64 --decode | jq '."REPO_TOKEN"'
86+
./vendor/bin/laminas repository:generate-data "$PLATFORM_VARIABLES" | base64 --decorede | jq '."REPO_TOKEN"'
8087
fi
8188
shutdown_timeout: 20
89+
generateintegration:
90+
# Refresh repository data every 6 hours (UTC).
91+
spec: '0 */6 * * *'
92+
commands:
93+
start: |
94+
if [ "$PLATFORM_BRANCH" = master ]; then
95+
./vendor/bin/laminas integration:seed-db
96+
fi
97+
shutdown_timeout: 20
98+
99+
operations:
100+
rebuildIntegrationDatabase:
101+
role: admin
102+
commands:
103+
start: ./vendor/bin/laminas integration:create-db --force-rebuild
82104

83105
web:
84106
locations:

ADD_INTEGRATION.md

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
# Adding your entry to the Laminas Integrations Page
2+
3+
You can list your packages on the [Laminas Integrations](https://getlaminas.org/integrations) page by following the steps below:
4+
> Note that all packages **must** be listed on [Packagist](https://packagist.org/)
5+
6+
- Add your entries as JSON objects to the `data/integration/integration-packages.json` file.
7+
- Each package **must** be added as a single object.
8+
- Entries **must** use the [template](#new-entry-template) for submission.
9+
- Submit a PR against the default branch.
10+
11+
The JSON file can be validated using the provided JSON schema `data/integration/integration-packages-schema.json`.
12+
13+
Use the following command to make sure your submission will be correctly built:
14+
15+
```bash
16+
./vendor/bin/laminas integration:create-db --github-token=<github_token> [--force-rebuild]
17+
```
18+
19+
The optional "--force-rebuild" flag will regenerate the database completely, not only add and/or remove packages
20+
21+
## New entry template
22+
23+
```json
24+
{
25+
"packagistUrl": "",
26+
"keywords": [],
27+
"homepage": null
28+
}
29+
```
30+
31+
### New entry fields description
32+
33+
- `packagistUrl` **(required)**
34+
**string** - the Packagist URL of the entry, with no query parameters
35+
36+
- `keywords`
37+
**array of strings** - optional user defined keywords used for filtering results
38+
39+
- `homepage`
40+
**string** - optional URL to package homepage, will overwrite "homepage" field from Packagist Api data
41+
42+
> Optional keys may be set to `null` or omitted altogether.

README.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,3 +86,7 @@ Everyone is welcome to post a blog entry. Once submitted, it will be reviewed by
8686
If it's rejected, the reason for the rejection will be included, so you can update it and resubmit the post if applicable.
8787

8888
The submission process is described in the [ADD_BLOG_ENTRY](ADD_BLOG_ENTRY.md) file.
89+
90+
## Adding packages to the Laminas Integrations Page
91+
92+
The [ADD INTEGRATION](ADD_INTEGRATION.md) file describes the process of adding packages to the [Laminas Integrations Page](https://getlaminas.org/integrations)

bootstrap/gulpfile.mjs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,9 @@ function js() {
5858
'node_modules/jquery/dist/jquery.slim.min.js',
5959
'node_modules/@popperjs/core/dist/umd/popper.min.js',
6060
'node_modules/bootstrap/dist/js/bootstrap.min.js',
61-
'js/base.js'
61+
'js/base.js',
62+
'js/_integration.js',
63+
'js/_maintenance-overview.js'
6264
]))
6365
.pipe(concat({path: 'laminas.js'}))
6466
.pipe(terser({mangle: false}).on('error', function (e) {

bootstrap/js/_integration.js

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
'use strict';
2+
3+
document.addEventListener('DOMContentLoaded', () => {
4+
const onIntegrationPage = document.querySelector('.package-button');
5+
if (! onIntegrationPage) {
6+
return;
7+
}
8+
9+
loadIntegrationPage();
10+
});
11+
12+
function loadIntegrationPage()
13+
{
14+
document.querySelectorAll('.package-button.type, .package-button.category, .package-button.usage').forEach(button => {
15+
button.addEventListener('click', handleFilters);
16+
})
17+
18+
document.querySelectorAll('.package-button.keyword').forEach(button => {
19+
button.addEventListener('click', handleKeywords);
20+
})
21+
22+
document.querySelectorAll('.integration-filter').forEach(button => {
23+
button.addEventListener('click', removeKeyword);
24+
})
25+
26+
document.querySelectorAll('#integration-pagination a').forEach(a => {
27+
const url = new URL(a.href)
28+
for (let [k,v] of new URLSearchParams(window.location.search).entries()) {
29+
if (k === 'keywords[]' || k === 'q' || k === 'type' || k === 'category' || k === 'usage') {
30+
url.searchParams.set(k,v);
31+
}
32+
}
33+
a.href = url.toString();
34+
})
35+
36+
document.querySelector('#clear-filters-button')?.addEventListener('click', function () {
37+
const url = new URL(window.location.href);
38+
39+
for (let [k,v] of new URLSearchParams(window.location.search).entries()) {
40+
if (k !== 'page') {
41+
url.searchParams.delete(k);
42+
}
43+
}
44+
45+
window.location.replace(url.toString());
46+
});
47+
48+
document.querySelector('#integration-search-btn').addEventListener('click', function () {
49+
setSearchQuery(document.querySelector('#integration-search').value);
50+
});
51+
52+
document.querySelector('#integration-search').addEventListener('keypress', function (e) {
53+
const search = this.value;
54+
if (e.which === 13) {
55+
setSearchQuery(search);
56+
}
57+
})
58+
59+
function handleFilters() {
60+
for (const filter of ['type', 'category', 'usage']) {
61+
if (this.classList.contains(filter)) {
62+
handleParams(filter, this.dataset.value);
63+
}
64+
}
65+
}
66+
67+
function handleParams(filterKey, filterValue) {
68+
const url = new URL(window.location.href);
69+
const params = new URLSearchParams(url.search);
70+
71+
if (filterValue === 'all' || params.get(filterKey) === filterValue) {
72+
url.searchParams.delete(filterKey);
73+
74+
window.location.replace(url.toString());
75+
} else if (! params.has(filterKey, filterValue)) {
76+
url.searchParams.set(filterKey, filterValue);
77+
78+
window.location.replace(url.toString());
79+
}
80+
}
81+
82+
function handleKeywords() {
83+
const url = new URL(window.location.href);
84+
const params = new URLSearchParams(url.search);
85+
const keyword = this.dataset.value;
86+
87+
if (! params.has("keywords[]", keyword)) {
88+
params.append("keywords[]", keyword);
89+
url.search = params.toString();
90+
91+
window.location.replace(url.toString());
92+
}
93+
}
94+
95+
function removeKeyword() {
96+
const url = new URL(window.location.href);
97+
const params = new URLSearchParams(url.search);
98+
const keyword = this.dataset.value;
99+
100+
if (params.has("keywords[]", keyword)) {
101+
params.delete("keywords[]", keyword);
102+
url.search = params.toString();
103+
104+
window.location.replace(url.toString());
105+
}
106+
}
107+
108+
function setSearchQuery(search) {
109+
const url = new URL(window.location.href);
110+
111+
url.searchParams.set('q', search);
112+
window.location.replace(url.toString());
113+
}
114+
}
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
'use strict';
2+
3+
document.addEventListener('DOMContentLoaded', () => {
4+
const onMaintenanceOverviewPage = document.querySelector('.status-button');
5+
if (! onMaintenanceOverviewPage) {
6+
return
7+
};
8+
9+
loadMaintenanceOverview();
10+
});
11+
12+
function loadMaintenanceOverview()
13+
{
14+
document.querySelectorAll('.status-button').forEach(button => {
15+
button.addEventListener('click', handleKeywords);
16+
})
17+
18+
function handleKeywords() {
19+
const url = new URL(window.location.href);
20+
const params = new URLSearchParams(url.search);
21+
const keyword = this.dataset.value;
22+
23+
if (! params.has("status[]", keyword)) {
24+
params.append("status[]", keyword);
25+
url.search = params.toString();
26+
27+
window.location.replace(url.toString());
28+
}
29+
}
30+
31+
document.querySelectorAll('.status-button.active').forEach(button => {
32+
button.addEventListener('click', removeStatus);
33+
})
34+
35+
function removeStatus() {
36+
const url = new URL(window.location.href);
37+
const params = new URLSearchParams(url.search);
38+
const status = this.dataset.value;
39+
40+
if (params.has("status[]", status)) {
41+
params.delete("status[]", status);
42+
url.search = params.toString();
43+
44+
window.location.replace(url.toString());
45+
}
46+
}
47+
48+
document.querySelector('#clear-filters-button')?.addEventListener('click', function () {
49+
const url = new URL(window.location.href);
50+
51+
for (let [k,v] of new URLSearchParams(window.location.search).entries()) {
52+
if (k !== 'page') {
53+
url.searchParams.delete(k);
54+
}
55+
}
56+
57+
window.location.replace(url.toString());
58+
});
59+
60+
document.querySelector('#package-search-btn').addEventListener('click', function () {
61+
setSearchQuery(document.querySelector('#package-search').value);
62+
});
63+
64+
document.querySelector('#package-search').addEventListener('keypress', function (e) {
65+
const search = this.value;
66+
if (e.which === 13) {
67+
setSearchQuery(search);
68+
}
69+
})
70+
71+
function setSearchQuery(search) {
72+
const url = new URL(window.location.href);
73+
74+
url.searchParams.set('q', search);
75+
window.location.replace(url.toString());
76+
}
77+
}

bootstrap/js/base.js

Lines changed: 0 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -11,69 +11,3 @@
1111
}
1212
});
1313
}
14-
15-
{
16-
document.querySelectorAll('.status-button').forEach(button => {
17-
button.addEventListener('click', handleKeywords);
18-
})
19-
20-
function handleKeywords() {
21-
const url = new URL(window.location.href);
22-
const params = new URLSearchParams(url.search);
23-
const keyword = this.dataset.value;
24-
25-
if (! params.has("status[]", keyword)) {
26-
params.append("status[]", keyword);
27-
url.search = params.toString();
28-
29-
window.location.replace(url.toString());
30-
}
31-
}
32-
33-
document.querySelectorAll('.status-button.active').forEach(button => {
34-
button.addEventListener('click', removeStatus);
35-
})
36-
37-
function removeStatus() {
38-
const url = new URL(window.location.href);
39-
const params = new URLSearchParams(url.search);
40-
const status = this.dataset.value;
41-
42-
if (params.has("status[]", status)) {
43-
params.delete("status[]", status);
44-
url.search = params.toString();
45-
46-
window.location.replace(url.toString());
47-
}
48-
}
49-
50-
document.querySelector('#clear-filters-button')?.addEventListener('click', function () {
51-
const url = new URL(window.location.href);
52-
53-
for (let [k,v] of new URLSearchParams(window.location.search).entries()) {
54-
if (k !== 'page') {
55-
url.searchParams.delete(k);
56-
}
57-
}
58-
59-
window.location.replace(url.toString());
60-
});
61-
62-
document.querySelector('#package-search-btn').addEventListener('click', function () {
63-
setSearchQuery(document.querySelector('#package-search').value);
64-
});
65-
66-
document.querySelector('#package-search').addEventListener('keypress', function (e) {
67-
const search = this.value;
68-
if (e.which === 13) {
69-
setSearchQuery(search);
70-
}
71-
})
72-
73-
function setSearchQuery(search) {
74-
const url = new URL(window.location.href);
75-
76-
url.searchParams.set('q', search);
77-
window.location.replace(url.toString());
78-
}
79-
}

0 commit comments

Comments
 (0)