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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e883077823ea13efc6672caf40765fd68d30d10f5c28e7770a91a7859159511f
4
- data.tar.gz: 9fca02e64c54d5d1d5ef27b8d57ca907c852d09e8197d4fdf5125a862db84aca
3
+ metadata.gz: c08f73c874e192ee0b1bede450515781124405c6875afec2ac4f6500e3a1db46
4
+ data.tar.gz: 55c89932abd93336991692769b088ca9dc798d04c2f7615cd175f67f156f06d4
5
5
  SHA512:
6
- metadata.gz: f3c860025a9557c736cbc3157a195d0e3c1fa0036d6eb944d99699326fb5f88f87ebe7891841a2fd870d56057a978dc00bb0b05b07f420bf0d9a3229874b675e
7
- data.tar.gz: 6778385016612559491561009245a8f248a037b77ce0ddadbf7702b159c729e128f87b896be49151973a177d9829910d8f7254e61319cd365ed6a3d4dd501e1b
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 `STUDIO_CONTENT_WORKFLOW` on the commit SHA (Waiting / Exporting / Live /
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 `STUDIO_CONTENT_WORKFLOW`, Actions read on the PAT, and
15
- the toolbar indicator states
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 "awaiting_run";
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
- * Look up the content-pipeline workflow run for a commit SHA.
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 workflowFile = String(env.STUDIO_CONTENT_WORKFLOW || "").trim();
250
- const ref = String(env.STUDIO_CONTENT_WORKFLOW_REF || "main").trim() || "main";
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 required (7–40 hex chars)");
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
- const runsPath =
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
- const state = mapWorkflowRunState(run);
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
 
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Pandorga
4
- VERSION = "1.4.4"
4
+ VERSION = "1.4.5"
5
5
  end
@@ -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?("STUDIO_CONTENT_WORKFLOW") || api_src.include?("STUDIO_CONTENT_WORKFLOW"),
37
- "pipeline status must read STUDIO_CONTENT_WORKFLOW")
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")