@styleflow.app/flowpress-deploy 1.0.0-beta.0 → 1.0.0-beta.10
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 +70 -0
- package/README.md +6 -12
- package/composer.json +1 -1
- package/containers/cms/Dockerfile +35 -0
- package/containers/cms/Dockerfile.dockerignore +20 -0
- package/containers/cms/healthz.php +17 -0
- package/containers/cms/install-languages.sh +16 -0
- package/containers/cms/mpm.conf +10 -0
- package/containers/cms/php.ini +16 -0
- package/containers/cms/sanitize.php +37 -0
- package/containers/cms/smtp.php +13 -0
- package/containers/cms/vhost.conf +61 -0
- package/containers/cms/wp-cli.yml +1 -0
- package/containers/php-runtime/Dockerfile +21 -0
- package/containers/web-ssr/Dockerfile +25 -0
- package/containers/web-ssr/Dockerfile.dockerignore +10 -0
- package/containers/web-static/Caddyfile +40 -0
- package/containers/web-static/Dockerfile +15 -0
- package/containers/web-static/Dockerfile.dockerignore +7 -0
- 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/ops/bootstrap.d.ts +21 -0
- package/dist/ops/bootstrap.d.ts.map +1 -0
- package/dist/ops/bootstrap.js +123 -0
- package/dist/ops/bootstrap.js.map +1 -0
- package/dist/ops/build.d.ts +30 -0
- package/dist/ops/build.d.ts.map +1 -0
- package/dist/ops/build.js +99 -0
- package/dist/ops/build.js.map +1 -0
- package/dist/ops/cli.d.ts +3 -0
- package/dist/ops/cli.d.ts.map +1 -0
- package/dist/ops/cli.js +140 -0
- package/dist/ops/cli.js.map +1 -0
- package/dist/ops/config.d.ts +54 -0
- package/dist/ops/config.d.ts.map +1 -0
- package/dist/ops/config.js +163 -0
- package/dist/ops/config.js.map +1 -0
- package/dist/ops/coolify.d.ts +2 -0
- package/dist/ops/coolify.d.ts.map +1 -0
- package/dist/ops/coolify.js +44 -0
- package/dist/ops/coolify.js.map +1 -0
- package/dist/ops/data.d.ts +27 -0
- package/dist/ops/data.d.ts.map +1 -0
- package/dist/ops/data.js +148 -0
- package/dist/ops/data.js.map +1 -0
- package/dist/ops/deploy.d.ts +18 -0
- package/dist/ops/deploy.d.ts.map +1 -0
- package/dist/ops/deploy.js +100 -0
- package/dist/ops/deploy.js.map +1 -0
- package/dist/ops/editorial.d.ts +23 -0
- package/dist/ops/editorial.d.ts.map +1 -0
- package/dist/ops/editorial.js +79 -0
- package/dist/ops/editorial.js.map +1 -0
- package/dist/ops/environment.d.ts +29 -0
- package/dist/ops/environment.d.ts.map +1 -0
- package/dist/ops/environment.js +159 -0
- package/dist/ops/environment.js.map +1 -0
- package/dist/ops/health.d.ts +25 -0
- package/dist/ops/health.d.ts.map +1 -0
- package/dist/ops/health.js +22 -0
- package/dist/ops/health.js.map +1 -0
- package/dist/ops/monitor.d.ts +23 -0
- package/dist/ops/monitor.d.ts.map +1 -0
- package/dist/ops/monitor.js +142 -0
- package/dist/ops/monitor.js.map +1 -0
- package/dist/ops/process.d.ts +9 -0
- package/dist/ops/process.d.ts.map +1 -0
- package/dist/ops/process.js +48 -0
- package/dist/ops/process.js.map +1 -0
- package/dist/ops/provision.d.ts +41 -0
- package/dist/ops/provision.d.ts.map +1 -0
- package/dist/ops/provision.js +257 -0
- package/dist/ops/provision.js.map +1 -0
- package/dist/ops/release.d.ts +26 -0
- package/dist/ops/release.d.ts.map +1 -0
- package/dist/ops/release.js +79 -0
- package/dist/ops/release.js.map +1 -0
- package/dist/ops/s3.d.ts +8 -0
- package/dist/ops/s3.d.ts.map +1 -0
- package/dist/ops/s3.js +38 -0
- package/dist/ops/s3.js.map +1 -0
- package/dist/ops/static-config.d.ts +14 -0
- package/dist/ops/static-config.d.ts.map +1 -0
- package/dist/ops/static-config.js +96 -0
- package/dist/ops/static-config.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 +98 -0
- package/includes/manual-queue.php +78 -0
- package/includes/queue-provider.php +621 -0
- package/package.json +50 -7
- package/schema/deployment.schema.json +45 -0
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Turns the static Astro build's Cloudflare-style `_redirects` and `_headers`
|
|
3
|
+
// into Caddy directives, plus the /media and /app/uploads proxies that the
|
|
4
|
+
// Pages Functions used to provide. Output: <dist>/.container/routes.caddy.
|
|
5
|
+
//
|
|
6
|
+
// APP_ENV=production node FlowPress Deploy apps/web/dist-production
|
|
7
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
8
|
+
import { resolve } from "node:path";
|
|
9
|
+
import { fileURLToPath } from "node:url";
|
|
10
|
+
function quote(value) {
|
|
11
|
+
if (/["\s{}]/.test(value) && !/^"[^"]*"$/.test(value)) {
|
|
12
|
+
return `"${value.replaceAll('"', '\\"')}"`;
|
|
13
|
+
}
|
|
14
|
+
return value;
|
|
15
|
+
}
|
|
16
|
+
// FlowPress routing only emits exact paths (see validateRedirects), so every
|
|
17
|
+
// line is "source target status". Both slash variants of a source redirect.
|
|
18
|
+
export function redirectsToCaddy(text) {
|
|
19
|
+
const lines = [];
|
|
20
|
+
let index = 0;
|
|
21
|
+
for (const raw of text.split(/\r?\n/)) {
|
|
22
|
+
const line = raw.trim();
|
|
23
|
+
if (!line || line.startsWith("#"))
|
|
24
|
+
continue;
|
|
25
|
+
const [source, target, status = "301", ...rest] = line.split(/\s+/);
|
|
26
|
+
if (rest.length || !source?.startsWith("/") || !target) {
|
|
27
|
+
throw new Error(`Unsupported _redirects line: ${line}`);
|
|
28
|
+
}
|
|
29
|
+
if (/[*:]/.test(source)) {
|
|
30
|
+
throw new Error(`Splats and placeholders are not supported: ${line}`);
|
|
31
|
+
}
|
|
32
|
+
if (!["301", "302", "307", "308"].includes(status)) {
|
|
33
|
+
throw new Error(`Unsupported redirect status: ${line}`);
|
|
34
|
+
}
|
|
35
|
+
const variants = source === "/"
|
|
36
|
+
? ["/"]
|
|
37
|
+
: [source.replace(/\/+$/, ""), `${source.replace(/\/+$/, "")}/`];
|
|
38
|
+
index += 1;
|
|
39
|
+
lines.push(`@redirect${index} path ${variants.map(quote).join(" ")}`);
|
|
40
|
+
lines.push(`redir @redirect${index} ${quote(target)} ${status}`);
|
|
41
|
+
}
|
|
42
|
+
return lines;
|
|
43
|
+
}
|
|
44
|
+
export function headersToCaddy(text) {
|
|
45
|
+
const lines = [];
|
|
46
|
+
let pattern = null;
|
|
47
|
+
for (const raw of text.split(/\r?\n/)) {
|
|
48
|
+
if (!raw.trim() || raw.trim().startsWith("#"))
|
|
49
|
+
continue;
|
|
50
|
+
if (!/^\s/.test(raw)) {
|
|
51
|
+
pattern = raw.trim();
|
|
52
|
+
if (!pattern.startsWith("/"))
|
|
53
|
+
throw new Error(`Unsupported _headers pattern: ${pattern}`);
|
|
54
|
+
continue;
|
|
55
|
+
}
|
|
56
|
+
if (!pattern)
|
|
57
|
+
throw new Error(`Header without a path pattern: ${raw.trim()}`);
|
|
58
|
+
const separator = raw.indexOf(":");
|
|
59
|
+
if (separator < 0)
|
|
60
|
+
throw new Error(`Unsupported _headers line: ${raw.trim()}`);
|
|
61
|
+
const name = raw.slice(0, separator).trim();
|
|
62
|
+
const value = raw.slice(separator + 1).trim();
|
|
63
|
+
const matcher = pattern === "/*" ? "*" : pattern;
|
|
64
|
+
lines.push(`header ${matcher} ${name} ${quote(value)}`);
|
|
65
|
+
}
|
|
66
|
+
return lines;
|
|
67
|
+
}
|
|
68
|
+
export function proxiesToCaddy({ media, backend }) {
|
|
69
|
+
const lines = [];
|
|
70
|
+
const mediaUrl = new URL(media);
|
|
71
|
+
const mediaBase = mediaUrl.pathname.replace(/\/+$/, "");
|
|
72
|
+
lines.push("@flowpress_media path_regexp flowpress_media ^/media(/.+\\.[a-zA-Z0-9]{1,8})$");
|
|
73
|
+
lines.push("handle @flowpress_media {");
|
|
74
|
+
lines.push(`\trewrite * ${mediaBase}{http.regexp.flowpress_media.1}`);
|
|
75
|
+
lines.push(`\treverse_proxy ${mediaUrl.origin} {`);
|
|
76
|
+
lines.push("\t\theader_up Host {upstream_hostport}");
|
|
77
|
+
lines.push("\t}");
|
|
78
|
+
lines.push("}");
|
|
79
|
+
const backendUrl = new URL(backend);
|
|
80
|
+
lines.push("handle /app/uploads/* {");
|
|
81
|
+
lines.push(`\treverse_proxy ${backendUrl.origin} {`);
|
|
82
|
+
lines.push("\t\theader_up Host {upstream_hostport}");
|
|
83
|
+
lines.push("\t}");
|
|
84
|
+
lines.push("}");
|
|
85
|
+
return lines;
|
|
86
|
+
}
|
|
87
|
+
export function buildRoutesCaddy({ redirects = "", headers = "", media, backend }) {
|
|
88
|
+
return [
|
|
89
|
+
"# Generated by FlowPress Deploy. Do not edit.",
|
|
90
|
+
...redirectsToCaddy(redirects),
|
|
91
|
+
...headersToCaddy(headers),
|
|
92
|
+
...proxiesToCaddy({ media, backend }),
|
|
93
|
+
"",
|
|
94
|
+
].join("\n");
|
|
95
|
+
}
|
|
96
|
+
//# sourceMappingURL=static-config.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"static-config.js","sourceRoot":"","sources":["../../src/ops/static-config.js"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
/** Match Wrangler's selective Worker-first patterns, including exclusions. */
|
|
2
|
+
export function invokesWorkerFirst(patterns: any, pathname: any): boolean;
|
|
3
|
+
export function validateWorkerAssets(config: any, staticPaths: any, reservedAreaEnabled?: boolean): void;
|
|
4
|
+
export function assertFreeRuntimeRelease(target: any, env: any): void;
|
|
5
|
+
//# sourceMappingURL=worker-assets.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"worker-assets.d.ts","sourceRoot":"","sources":["../src/worker-assets.js"],"names":[],"mappings":"AAAA,8EAA8E;AAC9E,0EAMC;AAED,yGAYC;AAED,sEAIC"}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/** Match Wrangler's selective Worker-first patterns, including exclusions. */
|
|
2
|
+
export function invokesWorkerFirst(patterns, pathname) {
|
|
3
|
+
const match = pattern => new RegExp(`^${pattern.replace(/[.+?^${}()|[\]\\]/g, "\\$&").replaceAll("*", ".*")}$`).test(pathname);
|
|
4
|
+
if (patterns === true)
|
|
5
|
+
return true;
|
|
6
|
+
if (!Array.isArray(patterns))
|
|
7
|
+
return false;
|
|
8
|
+
return patterns.some(pattern => !pattern.startsWith("!") && match(pattern))
|
|
9
|
+
&& !patterns.some(pattern => pattern.startsWith("!") && match(pattern.slice(1)));
|
|
10
|
+
}
|
|
11
|
+
export function validateWorkerAssets(config, staticPaths, reservedAreaEnabled = false) {
|
|
12
|
+
const patterns = config.assets?.run_worker_first;
|
|
13
|
+
if (config.assets?.binding !== "ASSETS" || !config.main)
|
|
14
|
+
throw new Error("Worker must have an ASSETS binding and compiled entrypoint");
|
|
15
|
+
if (reservedAreaEnabled && (patterns === true || patterns?.includes("/*")))
|
|
16
|
+
throw new Error("Public assets must bypass the Worker");
|
|
17
|
+
for (const path of (reservedAreaEnabled ? ["/area-riservata/", "/area-riservata/example/", "/api/flowpress/reserved-area/login", "/api/flowpress/reserved-area/logout"] : [])) {
|
|
18
|
+
if (!invokesWorkerFirst(patterns, path))
|
|
19
|
+
throw new Error(`Protected runtime route missing: ${path}`);
|
|
20
|
+
}
|
|
21
|
+
for (const path of staticPaths) {
|
|
22
|
+
if (path.startsWith("/area-riservata/") || path.startsWith("/api/flowpress/reserved-area/"))
|
|
23
|
+
throw new Error(`Private static asset forbidden: ${path}`);
|
|
24
|
+
if (reservedAreaEnabled && invokesWorkerFirst(patterns, path))
|
|
25
|
+
throw new Error(`Static asset invokes Worker: ${path}`);
|
|
26
|
+
}
|
|
27
|
+
if (reservedAreaEnabled && (config.kv_namespaces?.length || config.images))
|
|
28
|
+
throw new Error("Free runtime must not provision session KV or Images bindings");
|
|
29
|
+
}
|
|
30
|
+
export function assertFreeRuntimeRelease(target, env) {
|
|
31
|
+
if (target.environmentName === "production" && target.projectType === "workers" && env.FLOWPRESS_RESERVED_AREA_ENABLED === "true" && env.CF_FREE_RUNTIME_VERIFIED !== "true") {
|
|
32
|
+
throw new Error("Production Workers release is gated: validate staging CPU (p95 < 8 ms, p99 < 10 ms, no exceededCpu), then set CF_FREE_RUNTIME_VERIFIED=true in the production environment.");
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
//# sourceMappingURL=worker-assets.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"worker-assets.js","sourceRoot":"","sources":["../src/worker-assets.js"],"names":[],"mappings":"AAAA,8EAA8E;AAC9E,MAAM,UAAU,kBAAkB,CAAC,QAAQ,EAAE,QAAQ;IACnD,MAAM,KAAK,GAAG,OAAO,CAAC,EAAE,CAAC,IAAI,MAAM,CAAC,IAAI,OAAO,CAAC,OAAO,CAAC,oBAAoB,EAAE,MAAM,CAAC,CAAC,UAAU,CAAC,GAAG,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;IAC/H,IAAI,QAAQ,KAAK,IAAI;QAAE,OAAO,IAAI,CAAC;IACnC,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,QAAQ,CAAC;QAAE,OAAO,KAAK,CAAC;IAC3C,OAAO,QAAQ,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC,OAAO,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,KAAK,CAAC,OAAO,CAAC,CAAC;WACtE,CAAC,QAAQ,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC,OAAO,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AACrF,CAAC;AAED,MAAM,UAAU,oBAAoB,CAAC,MAAM,EAAE,WAAW,EAAE,mBAAmB,GAAG,KAAK;IACnF,MAAM,QAAQ,GAAG,MAAM,CAAC,MAAM,EAAE,gBAAgB,CAAC;IACjD,IAAI,MAAM,CAAC,MAAM,EAAE,OAAO,KAAK,QAAQ,IAAI,CAAC,MAAM,CAAC,IAAI;QAAE,MAAM,IAAI,KAAK,CAAC,4DAA4D,CAAC,CAAC;IACvI,IAAI,mBAAmB,IAAI,CAAC,QAAQ,KAAK,IAAI,IAAI,QAAQ,EAAE,QAAQ,CAAC,IAAI,CAAC,CAAC;QAAE,MAAM,IAAI,KAAK,CAAC,sCAAsC,CAAC,CAAC;IACpI,KAAK,MAAM,IAAI,IAAI,CAAC,mBAAmB,CAAC,CAAC,CAAC,CAAC,kBAAkB,EAAE,0BAA0B,EAAE,oCAAoC,EAAE,qCAAqC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC;QAC9K,IAAI,CAAC,kBAAkB,CAAC,QAAQ,EAAE,IAAI,CAAC;YAAE,MAAM,IAAI,KAAK,CAAC,oCAAoC,IAAI,EAAE,CAAC,CAAC;IACvG,CAAC;IACD,KAAK,MAAM,IAAI,IAAI,WAAW,EAAE,CAAC;QAC/B,IAAI,IAAI,CAAC,UAAU,CAAC,kBAAkB,CAAC,IAAI,IAAI,CAAC,UAAU,CAAC,+BAA+B,CAAC;YAAE,MAAM,IAAI,KAAK,CAAC,mCAAmC,IAAI,EAAE,CAAC,CAAC;QACxJ,IAAI,mBAAmB,IAAI,kBAAkB,CAAC,QAAQ,EAAE,IAAI,CAAC;YAAE,MAAM,IAAI,KAAK,CAAC,gCAAgC,IAAI,EAAE,CAAC,CAAC;IACzH,CAAC;IACD,IAAI,mBAAmB,IAAI,CAAC,MAAM,CAAC,aAAa,EAAE,MAAM,IAAI,MAAM,CAAC,MAAM,CAAC;QAAE,MAAM,IAAI,KAAK,CAAC,+DAA+D,CAAC,CAAC;AAC/J,CAAC;AAED,MAAM,UAAU,wBAAwB,CAAC,MAAM,EAAE,GAAG;IAClD,IAAI,MAAM,CAAC,eAAe,KAAK,YAAY,IAAI,MAAM,CAAC,WAAW,KAAK,SAAS,IAAI,GAAG,CAAC,+BAA+B,KAAK,MAAM,IAAI,GAAG,CAAC,wBAAwB,KAAK,MAAM,EAAE,CAAC;QAC7K,MAAM,IAAI,KAAK,CAAC,4KAA4K,CAAC,CAAC;IAChM,CAAC;AACH,CAAC"}
|
package/docs/README.md
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# FlowPress Deploy: documentazione
|
|
2
|
+
|
|
3
|
+
FlowPress Deploy collega le modifiche editoriali di WordPress ai workflow di
|
|
4
|
+
build senza salvare token o contenuti nella propria coda.
|
|
5
|
+
|
|
6
|
+
## Pagine
|
|
7
|
+
|
|
8
|
+
| Argomento | Documento |
|
|
9
|
+
| --- | --- |
|
|
10
|
+
| Modello e responsabilità | [Architettura](architecture.md) |
|
|
11
|
+
| Variabili richieste | [Configurazione](configuration.md) |
|
|
12
|
+
| Deduplica, URI e revisioni | [Queue WordPress](wordpress-queue.md) |
|
|
13
|
+
| Payload e callback | [Frontend e workflow](frontend-workflow.md) |
|
|
14
|
+
| Uso quotidiano | [Operazioni](operations.md) |
|
|
15
|
+
| Callback mancanti e retry | [Recovery](recovery.md) |
|
|
16
|
+
| Diagnostica | [Troubleshooting](troubleshooting.md) |
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
# Release iniziale deploy pack
|
|
2
|
+
|
|
3
|
+
- Obiettivo: estrarre build full/partial, baseline e guardie Cloudflare.
|
|
4
|
+
- Modifiche: package npm, MU-plugin Composer, test e workflow OIDC.
|
|
5
|
+
- Verifiche: typecheck, 7 test, npm pack, Composer validate e PHP lint positivi.
|
|
6
|
+
- Hardening pre-release: merge sicuro full/partial, sitemap, target
|
|
7
|
+
Pages/Workers, guardia `ASTRO_KEY` e lifecycle ack/release della queue.
|
|
8
|
+
- Workflow tag idempotente per il bootstrap della prima pubblicazione npm.
|
|
9
|
+
- Release prevista: `0.1.0-beta.0`; resta da configurare il Trusted Publisher npm.
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# Deploy su WordPress Core v1
|
|
2
|
+
|
|
3
|
+
## Esito
|
|
4
|
+
|
|
5
|
+
Preparata la major prerelease `1.0.0-beta.0` npm/Composer con dipendenza
|
|
6
|
+
limitata a `flowpress/wordpress-core ^1.0.0@beta` e senza adapter v0.x. Il
|
|
7
|
+
consumer canary mantiene il pin Core esatto.
|
|
8
|
+
|
|
9
|
+
## Scope
|
|
10
|
+
|
|
11
|
+
La logica Deploy non cambia. Versione package, Composer contract, lockfile,
|
|
12
|
+
changelog e documentazione vengono pubblicati dallo stesso tag.
|
|
13
|
+
|
|
14
|
+
## Verifiche
|
|
15
|
+
|
|
16
|
+
| Gate | Esito |
|
|
17
|
+
|---|---|
|
|
18
|
+
| `npm ci && npm run release:check` | OK; 7/7 test e pack 9 file |
|
|
19
|
+
| `npm audit` | OK; 0 vulnerabilita' dopo aggiornamento lockfile `nanoid` |
|
|
20
|
+
| `composer validate --strict --no-check-publish` | OK |
|
|
21
|
+
| PHP lint | OK |
|
|
22
|
+
| `git diff --check` | OK |
|
|
23
|
+
|
|
24
|
+
## Pubblicazione verificata
|
|
25
|
+
|
|
26
|
+
- PR v1 unita su `main`; merge e `gitHead` npm:
|
|
27
|
+
`24e0033d78566890263249e56da3c15449e80897`.
|
|
28
|
+
- Workflow Publish `32747502576`: verde con trusted publishing e provenance.
|
|
29
|
+
- npm: `@styleflow.app/flowpress-deploy@1.0.0-beta.0`, dist-tag `next`;
|
|
30
|
+
integrity, shasum, tarball e firma presenti. `latest` resta sulla linea
|
|
31
|
+
precedente.
|
|
32
|
+
- Repman: `flowpress/deploy@v1.0.0-beta.0`, sincronizzato dallo stesso tag e
|
|
33
|
+
con requisito `flowpress/wordpress-core ^1.0.0@beta`.
|
|
34
|
+
- Canary DMO: installazione npm/Composer da registry, manifest coerente e gate
|
|
35
|
+
completo verde.
|
|
36
|
+
|
|
37
|
+
## Rollback
|
|
38
|
+
|
|
39
|
+
La linea `0.1.0-beta.0` resta immutabile. I consumer v1 usano pin esatti e non
|
|
40
|
+
spostano `latest`.
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# FlowPress Deploy: revision-safe editorial queue
|
|
2
|
+
|
|
3
|
+
- Data: 2026-08-26 09:26 Europe/Rome
|
|
4
|
+
- Repository: `StyleFlow-App/flowpress-deploy`
|
|
5
|
+
- Versione prevista: `1.0.0-beta.1`
|
|
6
|
+
|
|
7
|
+
## Obiettivo
|
|
8
|
+
|
|
9
|
+
Rendere Deploy proprietario della queue editoriale tramite il provider Core,
|
|
10
|
+
senza cambiare Core e senza assumere configurazioni Bagful/Cloudflare.
|
|
11
|
+
|
|
12
|
+
## Modifiche
|
|
13
|
+
|
|
14
|
+
- Provider automatico, schema queue/state/batch e migrazione Core non
|
|
15
|
+
distruttiva.
|
|
16
|
+
- Fingerprint, URI history, WPML grouping, snapshot e ack revision-safe.
|
|
17
|
+
- Enqueue a fine richiesta, azioni manuali, UI operativa e GitHub test.
|
|
18
|
+
- Documentazione completa in `docs/` e inclusione nel pacchetto npm.
|
|
19
|
+
|
|
20
|
+
## Verifiche
|
|
21
|
+
|
|
22
|
+
- `php tests/php/queue-provider-test.php`: superato.
|
|
23
|
+
- `find . -name '*.php' ... php -l`: superato.
|
|
24
|
+
- `git diff --check`: superato.
|
|
25
|
+
- `composer validate --strict`: superato.
|
|
26
|
+
- `npm run release:check`: superato (typecheck, 7 test Vitest, test PHP,
|
|
27
|
+
pack dry-run con `docs/` e `includes/`).
|
|
28
|
+
|
|
29
|
+
## Verifica manuale
|
|
30
|
+
|
|
31
|
+
La verifica integrata in WordPress DMO sarà registrata nel report DMO; nessun
|
|
32
|
+
dispatch o deploy remoto è autorizzato in questo task.
|
|
33
|
+
|
|
34
|
+
## Commit suggerito
|
|
35
|
+
|
|
36
|
+
`feat: add revision-safe editorial build queue`
|
|
37
|
+
|
|
38
|
+
## Rischi e follow-up
|
|
39
|
+
|
|
40
|
+
Il rollout richiede configurazione remota dei token e un test editoriale
|
|
41
|
+
controllato prima di abilitare la pubblicazione production.
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# Architettura
|
|
2
|
+
|
|
3
|
+
## Perché
|
|
4
|
+
|
|
5
|
+
Core espone il contratto di dispatch e callback; Deploy possiede lo stato
|
|
6
|
+
editoriale necessario a non perdere aggiornamenti concorrenti.
|
|
7
|
+
|
|
8
|
+
## Come funziona
|
|
9
|
+
|
|
10
|
+
1. Il plugin registra il provider `flowpress-deploy` e lo seleziona.
|
|
11
|
+
2. Gli hook Core inoltrano post, termini e modifiche globali al provider.
|
|
12
|
+
3. Gli update vengono materializzati a `shutdown`, dopo meta e tassonomie.
|
|
13
|
+
4. Le tabelle `flowpress_deploy_queue`, `flowpress_deploy_state` e
|
|
14
|
+
`flowpress_deploy_batches` conservano rispettivamente lavoro, ultimo stato
|
|
15
|
+
distribuito e snapshot immutabile del batch.
|
|
16
|
+
5. Core effettua repository dispatch e callback usando il wire contract
|
|
17
|
+
pubblico. Deploy applica l’ack alla sola revisione fotografata.
|
|
18
|
+
|
|
19
|
+
La coda conserva hash SHA-256 e URI, non il contenuto completo o credenziali.
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# Configurazione
|
|
2
|
+
|
|
3
|
+
## Perché
|
|
4
|
+
|
|
5
|
+
Target e credenziali sono configurazione di ambiente e non devono finire nel
|
|
6
|
+
database WordPress.
|
|
7
|
+
|
|
8
|
+
## Come fare
|
|
9
|
+
|
|
10
|
+
1. Definire `SF_STATIC_TARGETS_JSON`, per esempio `['production']` in JSON
|
|
11
|
+
valido.
|
|
12
|
+
2. Definire `GITHUB_REPOSITORY_SLUG=organizzazione/repository`.
|
|
13
|
+
3. Definire `CMS_GITHUB_DISPATCH_TOKEN` con il minimo accesso necessario al
|
|
14
|
+
repository.
|
|
15
|
+
4. Definire `CMS_BUILD_CALLBACK_TOKEN` sia nel backend sia nel workflow.
|
|
16
|
+
5. Aprire **Strumenti → FlowPress Deploy**: lo stato indica soltanto presenza
|
|
17
|
+
o assenza dei token, mai il loro valore.
|
|
18
|
+
|
|
19
|
+
⚠️ Un target deve corrispondere esattamente a quello gestito dal workflow.
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# Frontend e workflow
|
|
2
|
+
|
|
3
|
+
## Wire contract
|
|
4
|
+
|
|
5
|
+
Il repository dispatch riceve:
|
|
6
|
+
|
|
7
|
+
```json
|
|
8
|
+
{
|
|
9
|
+
"batchId": "uuid",
|
|
10
|
+
"target": "production",
|
|
11
|
+
"mode": "partial",
|
|
12
|
+
"paths": ["/pagina/"],
|
|
13
|
+
"deletedPaths": ["/vecchio-slug/"],
|
|
14
|
+
"queueIds": [12]
|
|
15
|
+
}
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
Il workflow deve chiamare `POST /flowpress/v1/build-complete` con bearer token
|
|
19
|
+
e body `{"batchId":"uuid","status":"success"}` soltanto dopo build, deploy
|
|
20
|
+
e smoke test. In ogni altro caso invia `failure`.
|
|
21
|
+
|
|
22
|
+
⚠️ L’assenza del callback non equivale a successo: il batch resta in-flight e
|
|
23
|
+
sarà rilasciato dal recovery.
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# Operazioni
|
|
2
|
+
|
|
3
|
+
## Come fare
|
|
4
|
+
|
|
5
|
+
1. Aprire **Strumenti → FlowPress Deploy**.
|
|
6
|
+
2. Verificare repository, token, righe pending, URI e batch in-flight.
|
|
7
|
+
3. Espandere **Payload preview** prima dell’invio.
|
|
8
|
+
4. Usare **Pubblica queue** per la modalità prevista.
|
|
9
|
+
5. Usare **Force full** soltanto quando baseline o codice richiedono una build
|
|
10
|
+
completa; il browser chiede conferma.
|
|
11
|
+
6. Per forzare un singolo contenuto usare **Aggiungi alla build queue** nella
|
|
12
|
+
schermata o nella lista di post/termini.
|
|
13
|
+
|
|
14
|
+
Il pulsante **Test GitHub** verifica autenticazione e accesso al repository,
|
|
15
|
+
senza inviare un deployment.
|
package/docs/recovery.md
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# Recovery
|
|
2
|
+
|
|
3
|
+
## Callback failure
|
|
4
|
+
|
|
5
|
+
1. Il callback `failure` libera il batch.
|
|
6
|
+
2. Le righe tornano pending mantenendo l’ultima revisione editoriale.
|
|
7
|
+
3. Correggere la causa nel workflow e ripubblicare la queue.
|
|
8
|
+
|
|
9
|
+
## Callback mancante
|
|
10
|
+
|
|
11
|
+
1. Il cron orario cerca batch in-flight più vecchi di sei ore.
|
|
12
|
+
2. Il batch viene rilasciato come failure.
|
|
13
|
+
3. Il limite è modificabile con `flowpress_deploy_stale_batch_hours`.
|
|
14
|
+
|
|
15
|
+
⚠️ Non cancellare manualmente righe delle tre tabelle: si perderebbe la
|
|
16
|
+
corrispondenza fra stato distribuito e snapshot. Usare callback/retry.
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# Troubleshooting
|
|
2
|
+
|
|
3
|
+
## Queue vuota
|
|
4
|
+
|
|
5
|
+
Controllare `SF_STATIC_TARGETS_JSON`, tipo di contenuto instradabile e che il
|
|
6
|
+
salvataggio non sia autosave/revisione. Un salvataggio identico è correttamente
|
|
7
|
+
deduplicato.
|
|
8
|
+
|
|
9
|
+
## URI vecchio non eliminato
|
|
10
|
+
|
|
11
|
+
Controllare `previous_uris` nella pagina Deploy e che il workflow consumi
|
|
12
|
+
`deletedPaths`.
|
|
13
|
+
|
|
14
|
+
## Batch sempre in-flight
|
|
15
|
+
|
|
16
|
+
Verificare token callback e log del workflow. Attendere il recovery oppure
|
|
17
|
+
inviare esplicitamente `failure` per il `batchId` corretto.
|
|
18
|
+
|
|
19
|
+
## GitHub rifiuta il test
|
|
20
|
+
|
|
21
|
+
Verificare slug repository, scadenza e permessi del token. La pagina non mostra
|
|
22
|
+
né registra il segreto.
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# Queue WordPress
|
|
2
|
+
|
|
3
|
+
## Perché
|
|
4
|
+
|
|
5
|
+
Salvataggi ripetuti, traduzioni e cambi slug non devono produrre build inutili
|
|
6
|
+
o lasciare file vecchi online.
|
|
7
|
+
|
|
8
|
+
## Come funziona
|
|
9
|
+
|
|
10
|
+
1. Autosave e revisioni sono esclusi da Core.
|
|
11
|
+
2. Deploy raggruppa per post/term ID; con WPML usa il translation group.
|
|
12
|
+
3. Il fingerprint include contenuto, meta non volatili e tassonomie. Il filtro
|
|
13
|
+
`flowpress_deploy_volatile_meta_keys` aggiunge esclusioni.
|
|
14
|
+
4. `flowpress_deploy_fingerprint_fields`, `flowpress_deploy_group_key` e
|
|
15
|
+
`flowpress_deploy_canonical_uris` permettono estensioni senza patch.
|
|
16
|
+
5. `previous_uris` resta ancorato allo stato distribuito: uno slug change
|
|
17
|
+
produce il nuovo path e inserisce quello vecchio in `deletedPaths`.
|
|
18
|
+
6. Una modifica durante un batch incrementa la revisione. Il callback completa
|
|
19
|
+
lo snapshot, ma rimette la revisione nuova in pending.
|
|
20
|
+
|
|
21
|
+
La prima installazione importa le righe Core senza cancellarle; ogni vecchio
|
|
22
|
+
batch in-flight viene importato come pending per un retry sicuro.
|
package/flowpress-deploy.php
CHANGED
|
@@ -1,21 +1,50 @@
|
|
|
1
1
|
<?php
|
|
2
2
|
/**
|
|
3
3
|
* Plugin Name: FlowPress Deploy
|
|
4
|
-
* Description:
|
|
5
|
-
* Version:
|
|
4
|
+
* Description: Revision-safe editorial build queue and deployment capability for FlowPress.
|
|
5
|
+
* Version: 1.0.0-beta.5
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
if (! defined('ABSPATH')) {
|
|
9
9
|
exit;
|
|
10
10
|
}
|
|
11
11
|
|
|
12
|
+
define('FLOWPRESS_DEPLOY_VERSION', '1.0.0-beta.5');
|
|
13
|
+
define('FLOWPRESS_DEPLOY_FILE', __FILE__);
|
|
14
|
+
|
|
15
|
+
require_once __DIR__ . '/includes/queue-provider.php';
|
|
16
|
+
require_once __DIR__ . '/includes/manual-queue.php';
|
|
17
|
+
require_once __DIR__ . '/includes/admin.php';
|
|
18
|
+
require_once __DIR__ . '/includes/automatic-build.php';
|
|
19
|
+
|
|
20
|
+
add_action('muplugins_loaded', static function (): void {
|
|
21
|
+
if (! function_exists('flowpress_register_build_queue_provider')) {
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
flowpress_register_build_queue_provider('flowpress-deploy', [
|
|
25
|
+
'enqueue' => 'flowpress_deploy_collect_change',
|
|
26
|
+
'payload' => 'flowpress_deploy_build_payload',
|
|
27
|
+
'markDispatched' => 'flowpress_deploy_mark_dispatched',
|
|
28
|
+
'complete' => 'flowpress_deploy_complete_batch',
|
|
29
|
+
'pendingCount' => 'flowpress_deploy_pending_count',
|
|
30
|
+
]);
|
|
31
|
+
}, 110);
|
|
32
|
+
|
|
33
|
+
add_filter('flowpress_build_queue_provider', static fn (): string => 'flowpress-deploy', 100);
|
|
34
|
+
|
|
12
35
|
add_action('muplugins_loaded', static function (): void {
|
|
13
36
|
if (! function_exists('flowpress_register_feature')) {
|
|
14
37
|
return;
|
|
15
38
|
}
|
|
16
39
|
flowpress_register_feature('deploy', [
|
|
17
|
-
'version' =>
|
|
18
|
-
'capabilities' => [
|
|
40
|
+
'version' => FLOWPRESS_DEPLOY_VERSION,
|
|
41
|
+
'capabilities' => [
|
|
42
|
+
'full-build',
|
|
43
|
+
'partial-build',
|
|
44
|
+
'build-callback',
|
|
45
|
+
'revision-safe-queue',
|
|
46
|
+
'manual-enqueue',
|
|
47
|
+
],
|
|
19
48
|
'config' => [
|
|
20
49
|
'targets' => function_exists('flowpress_static_targets')
|
|
21
50
|
? flowpress_static_targets()
|
|
@@ -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
|
+
}
|