pandorga 1.4.4 → 1.4.5
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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +13 -3
- data/functions/api/studio/_lib/github.js +115 -20
- data/lib/pandorga/version.rb +1 -1
- data/scripts/test/test-studio-api-contract.rb +8 -2
- data/studio/assets/{index-Cf9OPjQU.js → index-OAmrbW7i.js} +60 -60
- data/studio/index.html +1 -1
- data/studio-app/src/api/client.js +6 -4
- data/studio-app/src/main.js +72 -61
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: c08f73c874e192ee0b1bede450515781124405c6875afec2ac4f6500e3a1db46
|
|
4
|
+
data.tar.gz: 55c89932abd93336991692769b088ca9dc798d04c2f7615cd175f67f156f06d4
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: '00088be394eb9440225561ca538fd7aef974688c9563d293627b5f3e7ee1f5058c80a6ffb70734d7eaec249a1b50ccf81df68a8dce557519730d666b5da1e3a3'
|
|
7
|
+
data.tar.gz: a3a0687866fe28126982d3564595fa6e2193034a2bd0f2e288ccf2e19d180765d5a4e2ca3d8ae92604a8d1c4eb92cf81136076ca292077ad9e6e14d92fbceb3b
|
data/CHANGELOG.md
CHANGED
|
@@ -1,18 +1,28 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [1.4.5] — 2026-10-07
|
|
4
|
+
|
|
5
|
+
### Changed
|
|
6
|
+
|
|
7
|
+
- Studio pipeline chip is always visible (between Theme and Revert); toolbar
|
|
8
|
+
Delete control removed
|
|
9
|
+
- Pipeline workflow name comes from `_config.yml` → `pandorga.content.workflow`
|
|
10
|
+
(not a Pages secret); env override remains optional
|
|
11
|
+
- Pipeline status without `sha` returns the latest run on the branch
|
|
12
|
+
|
|
3
13
|
## [1.4.4] — 2026-10-07
|
|
4
14
|
|
|
5
15
|
### Added
|
|
6
16
|
|
|
7
17
|
- Studio content-pipeline status: after Save in production, poll GitHub Actions
|
|
8
|
-
for
|
|
18
|
+
for the content workflow on the commit SHA (Waiting / Exporting / Live /
|
|
9
19
|
Failed). Local Export keeps a real reexport button with the same sync colors.
|
|
10
20
|
- `GET /api/studio/pipeline-status?sha=` on the Studio Function
|
|
11
21
|
|
|
12
22
|
### Changed
|
|
13
23
|
|
|
14
|
-
- Studio docs: document
|
|
15
|
-
|
|
24
|
+
- Studio docs: document the pipeline indicator, Actions read on the PAT, and
|
|
25
|
+
toolbar states
|
|
16
26
|
|
|
17
27
|
## [1.4.3] — 2026-10-07
|
|
18
28
|
|
|
@@ -219,10 +219,10 @@ export async function listDir(env, dirPath) {
|
|
|
219
219
|
/**
|
|
220
220
|
* Map a GitHub Actions workflow run to Studio pipeline UI state.
|
|
221
221
|
* @param {{ status?: string, conclusion?: string|null }|null} run
|
|
222
|
-
* @returns {"awaiting_run"|"running"|"ok"|"error"}
|
|
222
|
+
* @returns {"awaiting_run"|"running"|"ok"|"error"|"idle"}
|
|
223
223
|
*/
|
|
224
224
|
export function mapWorkflowRunState(run) {
|
|
225
|
-
if (!run) return "
|
|
225
|
+
if (!run) return "idle";
|
|
226
226
|
const status = String(run.status || "");
|
|
227
227
|
if (status === "completed") {
|
|
228
228
|
return run.conclusion === "success" ? "ok" : "error";
|
|
@@ -231,23 +231,111 @@ export function mapWorkflowRunState(run) {
|
|
|
231
231
|
}
|
|
232
232
|
|
|
233
233
|
/**
|
|
234
|
-
*
|
|
234
|
+
* Read pandorga.content.workflow(+ workflow_ref) from site `_config.yml`.
|
|
235
|
+
* Tiny indent-aware peek — no YAML dependency in the Function.
|
|
236
|
+
*/
|
|
237
|
+
export function parseContentWorkflowFromConfig(yamlText) {
|
|
238
|
+
const lines = String(yamlText || "").split(/\r?\n/);
|
|
239
|
+
let inPandorga = false;
|
|
240
|
+
let pandorgaIndent = 0;
|
|
241
|
+
let inContent = false;
|
|
242
|
+
let contentIndent = 0;
|
|
243
|
+
let workflow = null;
|
|
244
|
+
let workflowRef = null;
|
|
245
|
+
|
|
246
|
+
for (const line of lines) {
|
|
247
|
+
if (!line.trim() || /^\s*#/.test(line)) continue;
|
|
248
|
+
const m = line.match(/^(\s*)([A-Za-z0-9_]+):\s*(.*?)\s*$/);
|
|
249
|
+
if (!m) continue;
|
|
250
|
+
const indent = m[1].length;
|
|
251
|
+
const key = m[2];
|
|
252
|
+
let val = m[3];
|
|
253
|
+
if (
|
|
254
|
+
(val.startsWith('"') && val.endsWith('"')) ||
|
|
255
|
+
(val.startsWith("'") && val.endsWith("'"))
|
|
256
|
+
) {
|
|
257
|
+
val = val.slice(1, -1);
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
if (!inPandorga) {
|
|
261
|
+
if (key === "pandorga" && indent === 0) {
|
|
262
|
+
inPandorga = true;
|
|
263
|
+
pandorgaIndent = indent;
|
|
264
|
+
}
|
|
265
|
+
continue;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
if (indent <= pandorgaIndent && key !== "pandorga") break;
|
|
269
|
+
|
|
270
|
+
if (key === "content" && indent > pandorgaIndent) {
|
|
271
|
+
inContent = true;
|
|
272
|
+
contentIndent = indent;
|
|
273
|
+
continue;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
if (inContent) {
|
|
277
|
+
if (indent <= contentIndent) {
|
|
278
|
+
inContent = false;
|
|
279
|
+
if (key === "content" && indent > pandorgaIndent) {
|
|
280
|
+
inContent = true;
|
|
281
|
+
contentIndent = indent;
|
|
282
|
+
}
|
|
283
|
+
continue;
|
|
284
|
+
}
|
|
285
|
+
if (key === "workflow" && val) workflow = val;
|
|
286
|
+
if (key === "workflow_ref" && val) workflowRef = val;
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
return { workflow, workflowRef };
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
/**
|
|
294
|
+
* Workflow file + branch: optional env override, else `_config.yml`
|
|
295
|
+
* (`pandorga.content.workflow`).
|
|
296
|
+
*/
|
|
297
|
+
export async function resolveContentWorkflowConfig(env) {
|
|
298
|
+
const envWorkflow = String(env.STUDIO_CONTENT_WORKFLOW || "").trim();
|
|
299
|
+
const envRef = String(env.STUDIO_CONTENT_WORKFLOW_REF || "").trim();
|
|
300
|
+
if (envWorkflow) {
|
|
301
|
+
return {
|
|
302
|
+
workflow: envWorkflow,
|
|
303
|
+
ref: envRef || "main",
|
|
304
|
+
source: "env",
|
|
305
|
+
};
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
try {
|
|
309
|
+
const file = await getFile(env, "_config.yml");
|
|
310
|
+
if (file?.content) {
|
|
311
|
+
const parsed = parseContentWorkflowFromConfig(file.content);
|
|
312
|
+
if (parsed.workflow) {
|
|
313
|
+
return {
|
|
314
|
+
workflow: parsed.workflow,
|
|
315
|
+
ref: parsed.workflowRef || envRef || "main",
|
|
316
|
+
source: "config",
|
|
317
|
+
};
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
} catch {
|
|
321
|
+
/* fall through to unconfigured */
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
return { workflow: null, ref: envRef || "main", source: null };
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
/**
|
|
328
|
+
* Look up the content-pipeline workflow run for a commit SHA, or the latest
|
|
329
|
+
* run on the branch when `sha` is omitted.
|
|
235
330
|
* Requires the PAT to have Actions read (fine-grained: Actions → Read).
|
|
236
331
|
*
|
|
237
332
|
* @param {object} env
|
|
238
|
-
* @param {string} sha
|
|
239
|
-
* @returns {Promise<{
|
|
240
|
-
* mode: "production",
|
|
241
|
-
* workflow: string|null,
|
|
242
|
-
* sha: string,
|
|
243
|
-
* state: "awaiting_run"|"running"|"ok"|"error"|"unconfigured",
|
|
244
|
-
* run: object|null,
|
|
245
|
-
* error: string|null
|
|
246
|
-
* }>}
|
|
333
|
+
* @param {string} [sha]
|
|
247
334
|
*/
|
|
248
335
|
export async function getContentPipelineStatus(env, sha) {
|
|
249
|
-
const
|
|
250
|
-
const
|
|
336
|
+
const resolved = await resolveContentWorkflowConfig(env);
|
|
337
|
+
const workflowFile = resolved.workflow;
|
|
338
|
+
const ref = resolved.ref || "main";
|
|
251
339
|
const commitSha = String(sha || "").trim().toLowerCase();
|
|
252
340
|
|
|
253
341
|
if (!workflowFile) {
|
|
@@ -258,10 +346,11 @@ export async function getContentPipelineStatus(env, sha) {
|
|
|
258
346
|
state: "unconfigured",
|
|
259
347
|
run: null,
|
|
260
348
|
error: null,
|
|
349
|
+
source: null,
|
|
261
350
|
};
|
|
262
351
|
}
|
|
263
|
-
if (!/^[0-9a-f]{7,40}$/.test(commitSha)) {
|
|
264
|
-
const err = new Error("sha
|
|
352
|
+
if (commitSha && !/^[0-9a-f]{7,40}$/.test(commitSha)) {
|
|
353
|
+
const err = new Error("sha must be 7–40 hex chars when provided");
|
|
265
354
|
err.status = 400;
|
|
266
355
|
err.code = "invalid_sha";
|
|
267
356
|
throw err;
|
|
@@ -279,6 +368,7 @@ export async function getContentPipelineStatus(env, sha) {
|
|
|
279
368
|
state: "unconfigured",
|
|
280
369
|
run: null,
|
|
281
370
|
error: "workflow_not_found",
|
|
371
|
+
source: resolved.source,
|
|
282
372
|
};
|
|
283
373
|
}
|
|
284
374
|
if (wfRes.status === 401 || wfRes.status === 403) {
|
|
@@ -299,11 +389,13 @@ export async function getContentPipelineStatus(env, sha) {
|
|
|
299
389
|
}
|
|
300
390
|
|
|
301
391
|
const workflow = await wfRes.json();
|
|
302
|
-
|
|
392
|
+
let runsPath =
|
|
303
393
|
`/actions/workflows/${workflow.id}/runs` +
|
|
304
394
|
`?branch=${encodeURIComponent(ref)}` +
|
|
305
|
-
`&head_sha=${encodeURIComponent(commitSha)}` +
|
|
306
395
|
`&per_page=5`;
|
|
396
|
+
if (commitSha) {
|
|
397
|
+
runsPath += `&head_sha=${encodeURIComponent(commitSha)}`;
|
|
398
|
+
}
|
|
307
399
|
const runsRes = await gh(env, runsPath);
|
|
308
400
|
if (runsRes.status === 401 || runsRes.status === 403) {
|
|
309
401
|
const text = await runsRes.text();
|
|
@@ -325,12 +417,14 @@ export async function getContentPipelineStatus(env, sha) {
|
|
|
325
417
|
const payload = await runsRes.json();
|
|
326
418
|
const runs = Array.isArray(payload.workflow_runs) ? payload.workflow_runs : [];
|
|
327
419
|
const run = runs[0] || null;
|
|
328
|
-
|
|
420
|
+
let state = mapWorkflowRunState(run);
|
|
421
|
+
// Watching a specific SHA with no run yet → waiting for the push trigger.
|
|
422
|
+
if (commitSha && !run) state = "awaiting_run";
|
|
329
423
|
|
|
330
424
|
return {
|
|
331
425
|
mode: "production",
|
|
332
426
|
workflow: workflowFile,
|
|
333
|
-
sha: commitSha,
|
|
427
|
+
sha: commitSha || (run?.head_sha ? String(run.head_sha).toLowerCase() : ""),
|
|
334
428
|
state,
|
|
335
429
|
run: run
|
|
336
430
|
? {
|
|
@@ -342,6 +436,7 @@ export async function getContentPipelineStatus(env, sha) {
|
|
|
342
436
|
}
|
|
343
437
|
: null,
|
|
344
438
|
error: null,
|
|
439
|
+
source: resolved.source,
|
|
345
440
|
};
|
|
346
441
|
}
|
|
347
442
|
|
data/lib/pandorga/version.rb
CHANGED
|
@@ -33,14 +33,20 @@ end
|
|
|
33
33
|
|
|
34
34
|
check!(gh_src.include?("getContentPipelineStatus"),
|
|
35
35
|
"github helper must expose getContentPipelineStatus")
|
|
36
|
-
check!(gh_src.include?("
|
|
37
|
-
"pipeline status must read
|
|
36
|
+
check!(gh_src.include?("parseContentWorkflowFromConfig"),
|
|
37
|
+
"pipeline status must read pandorga.content.workflow from _config.yml")
|
|
38
|
+
check!(gh_src.include?("resolveContentWorkflowConfig"),
|
|
39
|
+
"pipeline status must resolve workflow from config (env optional override)")
|
|
38
40
|
check!(gh_src.include?("mapWorkflowRunState"),
|
|
39
41
|
"pipeline status must map Actions run status to UI state")
|
|
40
42
|
check!(REPO_ROOT.join("studio-app/src/api/client.js").read.include?("pipelineStatus"),
|
|
41
43
|
"SPA client must call pipeline-status")
|
|
42
44
|
check!(REPO_ROOT.join("studio-app/src/main.js").read.include?("armPipelineWatch"),
|
|
43
45
|
"SPA must watch the content pipeline after save")
|
|
46
|
+
check!(REPO_ROOT.join("studio-app/src/main.js").read.include?("refreshPipelineStatus"),
|
|
47
|
+
"SPA must hydrate pipeline status on boot")
|
|
48
|
+
check!(!REPO_ROOT.join("studio-app/src/main.js").read.match?(/action:\s*"delete"/),
|
|
49
|
+
"toolbar must not include a Delete action")
|
|
44
50
|
|
|
45
51
|
check!(api_src.include?("media/folder") || api_src.include?('"folder"'),
|
|
46
52
|
"API router should expose media folder create")
|