@styleflow.app/flowpress-deploy 1.0.0-beta.0 → 1.0.0-beta.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/CHANGELOG.md +18 -0
- package/README.md +3 -14
- package/composer.json +1 -1
- package/dist/frontend-assets.d.ts +2 -0
- package/dist/frontend-assets.d.ts.map +1 -0
- package/dist/frontend-assets.js +44 -0
- package/dist/frontend-assets.js.map +1 -0
- package/dist/frontend-links.d.ts +14 -0
- package/dist/frontend-links.d.ts.map +1 -0
- package/dist/frontend-links.js +82 -0
- package/dist/frontend-links.js.map +1 -0
- package/dist/worker-assets.d.ts +5 -0
- package/dist/worker-assets.d.ts.map +1 -0
- package/dist/worker-assets.js +35 -0
- package/dist/worker-assets.js.map +1 -0
- package/docs/README.md +16 -0
- package/docs/activities/2026-07-30-2339-initial-pack.md +9 -0
- package/docs/activities/2026-08-24-1745-wordpress-core-v1.md +40 -0
- package/docs/activities/2026-08-26-0926-revision-safe-queue.md +41 -0
- package/docs/activities/README.md +5 -0
- package/docs/architecture.md +19 -0
- package/docs/configuration.md +19 -0
- package/docs/frontend-workflow.md +23 -0
- package/docs/operations.md +15 -0
- package/docs/recovery.md +16 -0
- package/docs/troubleshooting.md +22 -0
- package/docs/wordpress-queue.md +22 -0
- package/flowpress-deploy.php +33 -4
- package/includes/admin.php +110 -0
- package/includes/automatic-build.php +81 -0
- package/includes/manual-queue.php +78 -0
- package/includes/queue-provider.php +621 -0
- package/package.json +26 -4
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
<?php
|
|
2
|
+
|
|
3
|
+
if (! defined('ABSPATH')) {
|
|
4
|
+
exit;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
add_action('admin_menu', static function (): void {
|
|
8
|
+
remove_submenu_page('tools.php', 'flowpress-builds');
|
|
9
|
+
add_management_page(
|
|
10
|
+
'FlowPress Deploy',
|
|
11
|
+
'FlowPress Deploy',
|
|
12
|
+
'manage_options',
|
|
13
|
+
'flowpress-builds',
|
|
14
|
+
'flowpress_deploy_render_admin_page'
|
|
15
|
+
);
|
|
16
|
+
}, 100);
|
|
17
|
+
|
|
18
|
+
add_action('admin_post_flowpress_deploy_test_github', static function (): void {
|
|
19
|
+
if (! current_user_can('manage_options')) {
|
|
20
|
+
wp_die('Forbidden', 403);
|
|
21
|
+
}
|
|
22
|
+
check_admin_referer('flowpress_deploy_test_github');
|
|
23
|
+
$repository = flowpress_env('GITHUB_REPOSITORY_SLUG');
|
|
24
|
+
$token = flowpress_env('CMS_GITHUB_DISPATCH_TOKEN');
|
|
25
|
+
$response = ($repository !== '' && $token !== '')
|
|
26
|
+
? wp_remote_get('https://api.github.com/repos/' . $repository, [
|
|
27
|
+
'timeout' => 15,
|
|
28
|
+
'headers' => [
|
|
29
|
+
'Accept' => 'application/vnd.github+json',
|
|
30
|
+
'Authorization' => 'Bearer ' . $token,
|
|
31
|
+
'User-Agent' => 'FlowPress-Deploy/' . FLOWPRESS_DEPLOY_VERSION,
|
|
32
|
+
],
|
|
33
|
+
])
|
|
34
|
+
: new WP_Error('missing_config', 'Configurazione GitHub incompleta.');
|
|
35
|
+
$ok = ! is_wp_error($response) && wp_remote_retrieve_response_code($response) === 200;
|
|
36
|
+
set_transient('flowpress_build_notice_' . get_current_user_id(), [
|
|
37
|
+
'type' => $ok ? 'success' : 'error',
|
|
38
|
+
'message' => $ok ? 'Connessione GitHub verificata.' : (is_wp_error($response) ? $response->get_error_message() : 'GitHub ha rifiutato la richiesta.'),
|
|
39
|
+
], 60);
|
|
40
|
+
wp_safe_redirect(admin_url('tools.php?page=flowpress-builds'));
|
|
41
|
+
exit;
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
function flowpress_deploy_render_admin_page(): void
|
|
45
|
+
{
|
|
46
|
+
if (! current_user_can('manage_options')) {
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
global $wpdb;
|
|
50
|
+
$notice = get_transient('flowpress_build_notice_' . get_current_user_id());
|
|
51
|
+
delete_transient('flowpress_build_notice_' . get_current_user_id());
|
|
52
|
+
$repository = flowpress_env('GITHUB_REPOSITORY_SLUG');
|
|
53
|
+
$has_dispatch_token = flowpress_env('CMS_GITHUB_DISPATCH_TOKEN') !== '';
|
|
54
|
+
$has_callback_token = flowpress_env('CMS_BUILD_CALLBACK_TOKEN') !== '';
|
|
55
|
+
echo '<div class="wrap"><h1>FlowPress Deploy</h1>';
|
|
56
|
+
if (is_array($notice)) {
|
|
57
|
+
printf('<div class="notice notice-%s"><p>%s</p></div>', esc_attr((string) $notice['type']), esc_html((string) $notice['message']));
|
|
58
|
+
}
|
|
59
|
+
echo '<p>La coda è deduplicata per contenuto logico e target. I token non vengono salvati né mostrati.</p>';
|
|
60
|
+
echo '<table class="widefat striped" style="max-width:900px"><tbody>';
|
|
61
|
+
printf('<tr><th>Repository GitHub</th><td>%s</td></tr>', esc_html($repository !== '' ? $repository : 'non configurato'));
|
|
62
|
+
printf('<tr><th>Token dispatch</th><td>%s</td></tr>', $has_dispatch_token ? 'configurato' : 'mancante');
|
|
63
|
+
printf('<tr><th>Token callback</th><td>%s</td></tr>', $has_callback_token ? 'configurato' : 'mancante');
|
|
64
|
+
echo '</tbody></table>';
|
|
65
|
+
echo '<form method="post" action="' . esc_url(admin_url('admin-post.php')) . '" style="margin:16px 0">';
|
|
66
|
+
wp_nonce_field('flowpress_deploy_test_github');
|
|
67
|
+
echo '<input type="hidden" name="action" value="flowpress_deploy_test_github">';
|
|
68
|
+
submit_button('Test GitHub', 'secondary', 'submit', false);
|
|
69
|
+
echo '</form>';
|
|
70
|
+
foreach (flowpress_static_targets() as $target) {
|
|
71
|
+
$payload = flowpress_deploy_build_payload($target, false);
|
|
72
|
+
$rows = (array) $wpdb->get_results($wpdb->prepare(
|
|
73
|
+
'SELECT * FROM ' . flowpress_deploy_queue_table() . ' WHERE target = %s ORDER BY updated_at DESC LIMIT 100',
|
|
74
|
+
$target
|
|
75
|
+
), ARRAY_A);
|
|
76
|
+
$in_flight = (int) $wpdb->get_var($wpdb->prepare(
|
|
77
|
+
"SELECT COUNT(*) FROM " . flowpress_deploy_batch_table() . " WHERE target = %s AND status = 'in_flight'",
|
|
78
|
+
$target
|
|
79
|
+
));
|
|
80
|
+
echo '<section style="margin:20px 0;padding:16px;background:#fff;border:1px solid #ccd0d4">';
|
|
81
|
+
printf('<h2>%s <small>(%d pending, %d in-flight)</small></h2>', esc_html($target), flowpress_deploy_pending_count($target), $in_flight);
|
|
82
|
+
echo '<p>Modalità prevista: <strong>' . esc_html($payload['mode']) . '</strong></p>';
|
|
83
|
+
echo '<details><summary>Payload preview</summary><pre style="white-space:pre-wrap">' . esc_html((string) wp_json_encode($payload, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES)) . '</pre></details>';
|
|
84
|
+
echo '<form method="post" action="' . esc_url(admin_url('admin-post.php')) . '" style="display:inline-block;margin:12px 8px 12px 0">';
|
|
85
|
+
wp_nonce_field('flowpress_publish_build');
|
|
86
|
+
echo '<input type="hidden" name="action" value="flowpress_publish_build"><input type="hidden" name="target" value="' . esc_attr($target) . '">';
|
|
87
|
+
submit_button('Pubblica queue', 'primary', 'submit', false);
|
|
88
|
+
echo '</form>';
|
|
89
|
+
echo '<form method="post" action="' . esc_url(admin_url('admin-post.php')) . '" style="display:inline-block" onsubmit="return confirm(\'Confermi la build completa?\')">';
|
|
90
|
+
wp_nonce_field('flowpress_publish_build');
|
|
91
|
+
echo '<input type="hidden" name="action" value="flowpress_publish_build"><input type="hidden" name="target" value="' . esc_attr($target) . '"><input type="hidden" name="force_full" value="1">';
|
|
92
|
+
submit_button('Force full', 'secondary', 'submit', false);
|
|
93
|
+
echo '</form>';
|
|
94
|
+
if ($rows !== []) {
|
|
95
|
+
echo '<table class="widefat striped"><thead><tr><th>Gruppo</th><th>URI correnti</th><th>URI precedenti</th><th>Rev.</th><th>Stato</th></tr></thead><tbody>';
|
|
96
|
+
foreach ($rows as $row) {
|
|
97
|
+
printf('<tr><td>%s</td><td>%s</td><td>%s</td><td>%d</td><td>%s</td></tr>',
|
|
98
|
+
esc_html((string) $row['group_key']),
|
|
99
|
+
esc_html(implode(', ', flowpress_deploy_decode_uris($row['current_uris'])) ?: '—'),
|
|
100
|
+
esc_html(implode(', ', flowpress_deploy_decode_uris($row['previous_uris'])) ?: '—'),
|
|
101
|
+
(int) $row['revision'],
|
|
102
|
+
$row['batch_id'] ? 'in-flight' : 'pending'
|
|
103
|
+
);
|
|
104
|
+
}
|
|
105
|
+
echo '</tbody></table>';
|
|
106
|
+
}
|
|
107
|
+
echo '</section>';
|
|
108
|
+
}
|
|
109
|
+
echo '</div>';
|
|
110
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
<?php
|
|
2
|
+
|
|
3
|
+
declare(strict_types=1);
|
|
4
|
+
defined('ABSPATH') || exit;
|
|
5
|
+
|
|
6
|
+
/** Debounce editorial changes; local fixtures never dispatch remote builds. */
|
|
7
|
+
function flowpress_deploy_schedule_public_build(int $delay = 60): void
|
|
8
|
+
{
|
|
9
|
+
if (! in_array(flowpress_env('APP_ENV'), ['staging', 'production'], true)
|
|
10
|
+
|| flowpress_env('CMS_GITHUB_DISPATCH_TOKEN') === ''
|
|
11
|
+
|| flowpress_env('GITHUB_REPOSITORY_SLUG') === ''
|
|
12
|
+
|| wp_next_scheduled('flowpress_deploy_dispatch_public_builds')) {
|
|
13
|
+
return;
|
|
14
|
+
}
|
|
15
|
+
wp_schedule_single_event(time() + $delay, 'flowpress_deploy_dispatch_public_builds');
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function flowpress_deploy_dispatch_public_builds(): void
|
|
19
|
+
{
|
|
20
|
+
if (! function_exists('flowpress_deploy_pending_count')) return;
|
|
21
|
+
global $wpdb;
|
|
22
|
+
foreach (flowpress_static_targets() as $target) {
|
|
23
|
+
if (flowpress_deploy_pending_count($target) === 0) continue;
|
|
24
|
+
$active = (int) $wpdb->get_var($wpdb->prepare(
|
|
25
|
+
'SELECT COUNT(*) FROM ' . flowpress_deploy_queue_table() . ' WHERE target = %s AND batch_id IS NOT NULL', $target
|
|
26
|
+
));
|
|
27
|
+
if ($active > 0) {
|
|
28
|
+
flowpress_deploy_schedule_public_build();
|
|
29
|
+
continue;
|
|
30
|
+
}
|
|
31
|
+
$result = flowpress_dispatch_build($target, true);
|
|
32
|
+
$failed = is_wp_error($result);
|
|
33
|
+
update_option('flowpress_deploy_build_dispatch_status', ['failed' => $failed, 'time' => time()], false);
|
|
34
|
+
if ($failed) {
|
|
35
|
+
error_log('FlowPress build dispatch failed: ' . $result->get_error_code());
|
|
36
|
+
flowpress_deploy_schedule_public_build(300);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
add_action('flowpress_deploy_dispatch_public_builds', 'flowpress_deploy_dispatch_public_builds');
|
|
41
|
+
|
|
42
|
+
add_action('save_post', static function (int $id, WP_Post $post): void {
|
|
43
|
+
if (! wp_is_post_revision($id) && ! wp_is_post_autosave($id)
|
|
44
|
+
&& in_array($post->post_type, array_merge(array_values(get_post_types(['public' => true])), ['wp_template', 'wp_template_part', 'wp_navigation', 'wp_block']), true)) {
|
|
45
|
+
flowpress_deploy_schedule_public_build();
|
|
46
|
+
}
|
|
47
|
+
}, 40, 2);
|
|
48
|
+
add_action('before_delete_post', static function (int $id, WP_Post $post): void {
|
|
49
|
+
if (in_array($post->post_type, array_merge(array_values(get_post_types(['public' => true])), ['wp_template', 'wp_template_part', 'wp_navigation', 'wp_block']), true)) flowpress_deploy_schedule_public_build();
|
|
50
|
+
}, 40, 2);
|
|
51
|
+
/** Public taxonomy edits can withdraw routes or change shared archive queries. */
|
|
52
|
+
function flowpress_deploy_taxonomy_affects_public_build(string $taxonomy): bool
|
|
53
|
+
{
|
|
54
|
+
$object = get_taxonomy($taxonomy);
|
|
55
|
+
return $object && (bool) apply_filters('flowpress_deploy_taxonomy_affects_public_build', $object->public, $taxonomy);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
add_action('set_object_terms', static function (int $id, $terms, $tt_ids, string $taxonomy): void {
|
|
59
|
+
if (flowpress_deploy_taxonomy_affects_public_build($taxonomy)) flowpress_deploy_schedule_public_build();
|
|
60
|
+
}, 40, 4);
|
|
61
|
+
foreach (['created_term', 'edited_term', 'pre_delete_term'] as $hook) {
|
|
62
|
+
$callback = $hook === 'pre_delete_term'
|
|
63
|
+
? static function (int $id, string $taxonomy): void {
|
|
64
|
+
if (flowpress_deploy_taxonomy_affects_public_build($taxonomy)) flowpress_deploy_schedule_public_build();
|
|
65
|
+
}
|
|
66
|
+
: static function (int $id, int $tt_id, string $taxonomy): void {
|
|
67
|
+
if (flowpress_deploy_taxonomy_affects_public_build($taxonomy)) flowpress_deploy_schedule_public_build();
|
|
68
|
+
};
|
|
69
|
+
add_action($hook, $callback, 40, $hook === 'pre_delete_term' ? 2 : 3);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
add_action('admin_notices', static function (): void {
|
|
73
|
+
$status = get_option('flowpress_deploy_build_dispatch_status', []);
|
|
74
|
+
if (! empty($status['failed']) && current_user_can('edit_others_posts')) {
|
|
75
|
+
echo '<div class="notice notice-warning"><p>Avvio automatico della build non riuscito. Controlla la connessione GitHub in <a href="'
|
|
76
|
+
. esc_url(admin_url('tools.php?page=flowpress-builds')) . '">FlowPress Deploy</a>. Le modifiche pubbliche attendono il deploy riuscito.</p></div>';
|
|
77
|
+
}
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
add_action('flowpress_build_change_enqueued', 'flowpress_deploy_schedule_public_build');
|
|
81
|
+
add_action('save_post_wp_block', static function():void { flowpress_deploy_schedule_public_build(); });
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
<?php
|
|
2
|
+
|
|
3
|
+
if (! defined('ABSPATH')) {
|
|
4
|
+
exit;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
function flowpress_deploy_manual_url(string $type, int $id): string
|
|
8
|
+
{
|
|
9
|
+
return wp_nonce_url(
|
|
10
|
+
admin_url('admin-post.php?action=flowpress_deploy_enqueue&type=' . rawurlencode($type) . '&id=' . $id),
|
|
11
|
+
'flowpress_deploy_enqueue_' . $type . '_' . $id
|
|
12
|
+
);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function flowpress_deploy_post_action(array $actions, WP_Post $post): array
|
|
16
|
+
{
|
|
17
|
+
if (current_user_can('edit_post', $post->ID)) {
|
|
18
|
+
$actions['flowpress_deploy_enqueue'] = '<a href="' . esc_url(flowpress_deploy_manual_url('post', $post->ID)) . '">Aggiungi alla build queue</a>';
|
|
19
|
+
}
|
|
20
|
+
return $actions;
|
|
21
|
+
}
|
|
22
|
+
add_filter('post_row_actions', 'flowpress_deploy_post_action', 20, 2);
|
|
23
|
+
add_filter('page_row_actions', 'flowpress_deploy_post_action', 20, 2);
|
|
24
|
+
|
|
25
|
+
add_action('post_submitbox_misc_actions', static function (WP_Post $post): void {
|
|
26
|
+
if (! current_user_can('edit_post', $post->ID)) {
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
echo '<div class="misc-pub-section"><a class="button" href="'
|
|
30
|
+
. esc_url(flowpress_deploy_manual_url('post', $post->ID))
|
|
31
|
+
. '">Aggiungi alla build queue</a></div>';
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
add_filter('term_row_actions', static function (array $actions, WP_Term $term): array {
|
|
35
|
+
$taxonomy = get_taxonomy($term->taxonomy);
|
|
36
|
+
if ($taxonomy && current_user_can($taxonomy->cap->edit_terms)) {
|
|
37
|
+
$actions['flowpress_deploy_enqueue'] = '<a href="'
|
|
38
|
+
. esc_url(flowpress_deploy_manual_url('term-' . $term->taxonomy, $term->term_id))
|
|
39
|
+
. '">Aggiungi alla build queue</a>';
|
|
40
|
+
}
|
|
41
|
+
return $actions;
|
|
42
|
+
}, 20, 2);
|
|
43
|
+
|
|
44
|
+
add_action('admin_post_flowpress_deploy_enqueue', static function (): void {
|
|
45
|
+
$type = sanitize_key((string) ($_GET['type'] ?? ''));
|
|
46
|
+
$id = max(0, (int) ($_GET['id'] ?? 0));
|
|
47
|
+
check_admin_referer('flowpress_deploy_enqueue_' . $type . '_' . $id);
|
|
48
|
+
$uri = '';
|
|
49
|
+
$resource_type = '';
|
|
50
|
+
if ($type === 'post' && current_user_can('edit_post', $id)) {
|
|
51
|
+
$post = get_post($id);
|
|
52
|
+
if ($post instanceof WP_Post) {
|
|
53
|
+
$resource_type = $post->post_type;
|
|
54
|
+
$uri = (string) get_permalink($post);
|
|
55
|
+
}
|
|
56
|
+
} elseif (str_starts_with($type, 'term-')) {
|
|
57
|
+
$taxonomy = substr($type, 5);
|
|
58
|
+
$object = get_taxonomy($taxonomy);
|
|
59
|
+
if ($object && current_user_can($object->cap->edit_terms)) {
|
|
60
|
+
$term = get_term($id, $taxonomy);
|
|
61
|
+
$link = $term instanceof WP_Term ? get_term_link($term) : '';
|
|
62
|
+
if (! is_wp_error($link)) {
|
|
63
|
+
$resource_type = $taxonomy;
|
|
64
|
+
$uri = (string) $link;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
if ($resource_type === '' || $uri === '') {
|
|
69
|
+
wp_die('Forbidden', 403);
|
|
70
|
+
}
|
|
71
|
+
flowpress_deploy_enqueue_change($resource_type, $id, $uri, 'update', false);
|
|
72
|
+
set_transient('flowpress_build_notice_' . get_current_user_id(), [
|
|
73
|
+
'type' => 'success',
|
|
74
|
+
'message' => 'Contenuto aggiunto alla build queue.',
|
|
75
|
+
], 60);
|
|
76
|
+
wp_safe_redirect(wp_get_referer() ?: admin_url('tools.php?page=flowpress-builds'));
|
|
77
|
+
exit;
|
|
78
|
+
});
|