@decocms/blocks-cli 7.50.0 → 7.50.1
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/package.json +3 -3
- package/scripts/migrate/phase-scaffold.ts +4 -4
- package/scripts/migrate/phase-verify.ts +1 -1
- package/scripts/migrate/templates/ci-workflows.test.ts +21 -10
- package/scripts/migrate/templates/{content-sync-yml.ts → sync-blocks-bot-yml.ts} +44 -27
- package/scripts/{pull-decofile.test.ts → sync-blocks-bot.test.ts} +21 -3
- package/scripts/{pull-decofile.ts → sync-blocks-bot.ts} +30 -20
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@decocms/blocks-cli",
|
|
3
|
-
"version": "7.50.
|
|
3
|
+
"version": "7.50.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Deco codegen (generate-blocks, generate-schema, generate-invoke) and Fresh-to-TanStack migration tooling",
|
|
6
6
|
"repository": {
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
"deco-audit-observability": "./scripts/audit-observability-config.ts",
|
|
18
18
|
"deco-migrate-blocks-to-kv": "./scripts/migrate-blocks-to-kv.ts",
|
|
19
19
|
"deco-sync-blocks-to-kv": "./scripts/sync-blocks-to-kv.ts",
|
|
20
|
-
"deco-
|
|
20
|
+
"deco-sync-blocks-bot": "./scripts/sync-blocks-bot.ts",
|
|
21
21
|
"deco-upgrade-6-to-7": "./scripts/upgrade-6-to-7.ts",
|
|
22
22
|
"deco-reconcile": "./scripts/reconcile.ts"
|
|
23
23
|
},
|
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
"lint:unused": "knip"
|
|
34
34
|
},
|
|
35
35
|
"dependencies": {
|
|
36
|
-
"@decocms/blocks": "7.50.
|
|
36
|
+
"@decocms/blocks": "7.50.1",
|
|
37
37
|
"ts-morph": "^27.0.0",
|
|
38
38
|
"tsx": "^4.22.5"
|
|
39
39
|
},
|
|
@@ -9,7 +9,7 @@ import { generateCacheConfig } from "./templates/cache-config";
|
|
|
9
9
|
import { generateCiFiles } from "./templates/ci-yml";
|
|
10
10
|
import { generateCommerceInit } from "./templates/commerce-init";
|
|
11
11
|
import { generateCommerceLoaders } from "./templates/commerce-loaders";
|
|
12
|
-
import {
|
|
12
|
+
import { generateSyncBlocksBotYml } from "./templates/sync-blocks-bot-yml";
|
|
13
13
|
import { generateMigrationPolicyPointerRule } from "./templates/cursor-rules";
|
|
14
14
|
import { generateHooks } from "./templates/hooks";
|
|
15
15
|
import { generateKnipConfig } from "./templates/knip-config";
|
|
@@ -133,11 +133,11 @@ export function scaffold(ctx: MigrationContext): void {
|
|
|
133
133
|
|
|
134
134
|
// Daily content pull from the still-live storefront (`<origin>/.decofile` ->
|
|
135
135
|
// `.deco/blocks` -> PR). Replaces the legacy cross-repo-PAT push sync; inert
|
|
136
|
-
// until the operator sets the repo variable
|
|
136
|
+
// until the operator sets the repo variable SYNC_BLOCKS_ORIGIN.
|
|
137
137
|
writeFile(
|
|
138
138
|
ctx,
|
|
139
|
-
".github/workflows/
|
|
140
|
-
|
|
139
|
+
".github/workflows/sync-blocks-bot.yml",
|
|
140
|
+
generateSyncBlocksBotYml(CANONICAL_BUN_VERSION),
|
|
141
141
|
);
|
|
142
142
|
|
|
143
143
|
// Server entry files (server.ts, worker-entry.ts, router.tsx, runtime.ts, context.ts)
|
|
@@ -26,7 +26,7 @@ const REQUIRED_FILES = [
|
|
|
26
26
|
".github/workflows/playwright.yml",
|
|
27
27
|
".github/workflows/react-doctor.yml",
|
|
28
28
|
".github/workflows/parity.yml",
|
|
29
|
-
".github/workflows/
|
|
29
|
+
".github/workflows/sync-blocks-bot.yml",
|
|
30
30
|
"tools/gates/no-suppressions.sh",
|
|
31
31
|
"playwright.config.ts",
|
|
32
32
|
"knip.config.ts",
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { describe, expect, it } from "vitest";
|
|
2
2
|
import { generateCiFiles } from "./ci-yml";
|
|
3
|
-
import { generateContentSyncYml } from "./content-sync-yml";
|
|
4
3
|
import { generateMainPushGuardYml } from "./main-push-guard-yml";
|
|
5
4
|
import { generateParityYml } from "./parity-yml";
|
|
6
5
|
import { generatePlaywrightFiles } from "./playwright-yml";
|
|
7
6
|
import { generateReactDoctorYml } from "./react-doctor-yml";
|
|
7
|
+
import { generateSyncBlocksBotYml } from "./sync-blocks-bot-yml";
|
|
8
8
|
|
|
9
9
|
describe("generateCiFiles", () => {
|
|
10
10
|
const files = generateCiFiles("22.15.0", "1.3.5");
|
|
@@ -125,15 +125,15 @@ describe("generateParityYml", () => {
|
|
|
125
125
|
});
|
|
126
126
|
});
|
|
127
127
|
|
|
128
|
-
describe("
|
|
129
|
-
const yml =
|
|
128
|
+
describe("generateSyncBlocksBotYml", () => {
|
|
129
|
+
const yml = generateSyncBlocksBotYml("bun@1.3.5");
|
|
130
130
|
|
|
131
|
-
it("pulls on a daily cron, gated on
|
|
132
|
-
expect(yml).toContain("name:
|
|
131
|
+
it("pulls on a daily cron, gated on SYNC_BLOCKS_ORIGIN", () => {
|
|
132
|
+
expect(yml).toContain("name: sync-blocks-bot");
|
|
133
133
|
expect(yml).toContain('BUN_VERSION: "1.3.5"'); // bun@ prefix stripped
|
|
134
134
|
expect(yml).toContain('- cron: "0 6 * * *"');
|
|
135
|
-
expect(yml).toContain("vars.
|
|
136
|
-
expect(yml).toContain("scripts/
|
|
135
|
+
expect(yml).toContain("vars.SYNC_BLOCKS_ORIGIN");
|
|
136
|
+
expect(yml).toContain("scripts/sync-blocks-bot.ts");
|
|
137
137
|
expect(yml).toContain("--fail-on-plaintext-secret");
|
|
138
138
|
});
|
|
139
139
|
|
|
@@ -160,9 +160,20 @@ describe("generateContentSyncYml", () => {
|
|
|
160
160
|
});
|
|
161
161
|
|
|
162
162
|
it("runs the site's own installed CLI, or a pinned one when given", () => {
|
|
163
|
-
expect(yml).toContain("bunx tsx node_modules/@decocms/blocks-cli/scripts/
|
|
164
|
-
const pinned =
|
|
165
|
-
|
|
163
|
+
expect(yml).toContain("bunx tsx node_modules/@decocms/blocks-cli/scripts/sync-blocks-bot.ts");
|
|
164
|
+
const pinned = generateSyncBlocksBotYml("1.3.5", "7.51.0");
|
|
165
|
+
// `npx --package=<pkg> <bin>`, never `bunx <pkg> <bin>` — the latter treats
|
|
166
|
+
// the bin name as an argument and silently runs deco-migrate instead.
|
|
167
|
+
expect(pinned).toContain("npx --yes --package=@decocms/blocks-cli@7.51.0 deco-sync-blocks-bot");
|
|
168
|
+
expect(pinned).not.toMatch(/bunx\s+(-y\s+)?@decocms\/blocks-cli/);
|
|
166
169
|
expect(pinned).not.toContain("node_modules/@decocms/blocks-cli");
|
|
167
170
|
});
|
|
171
|
+
|
|
172
|
+
it("cannot pass green having run the wrong tool", () => {
|
|
173
|
+
// pipefail so `| tee` doesn't swallow the plaintext-secret exit 1, plus a
|
|
174
|
+
// grep on the report that only this script emits.
|
|
175
|
+
expect(yml).toContain("set -o pipefail");
|
|
176
|
+
expect(yml).toContain("--json --github");
|
|
177
|
+
expect(yml).toContain(`grep -q '"remoteBlocks"'`);
|
|
178
|
+
});
|
|
168
179
|
});
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Scaffolds `.github/workflows/
|
|
2
|
+
* Scaffolds `.github/workflows/sync-blocks-bot.yml` — the secure content channel
|
|
3
3
|
* from the still-live Fresh/Deno storefront into the migrated repo.
|
|
4
4
|
*
|
|
5
5
|
* Replaces the legacy push-based sync (a workflow in the *legacy* repo holding a
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
*
|
|
10
10
|
* Direction is inverted — this repo pulls, on its own schedule, with its own
|
|
11
11
|
* `GITHUB_TOKEN`, and gates the result:
|
|
12
|
-
* - `
|
|
12
|
+
* - `sync-blocks-bot.ts` fetches `<origin>/.decofile` (public, unauthenticated)
|
|
13
13
|
* and writes `.deco/blocks/`, denying the `Site` block and anything holding
|
|
14
14
|
* encrypted credentials, and aborting on a plaintext credential;
|
|
15
15
|
* - a path guard fails the run if anything outside `.deco/blocks/` changed;
|
|
@@ -18,37 +18,44 @@
|
|
|
18
18
|
* `pull_request` workflows, so gating on the PR's own CI would need a PAT
|
|
19
19
|
* and would never fire on its own.
|
|
20
20
|
*
|
|
21
|
-
* Inert until the operator sets the repo variable `
|
|
21
|
+
* Inert until the operator sets the repo variable `SYNC_BLOCKS_ORIGIN` (same
|
|
22
22
|
* one-knob pattern as parity.yml's `PARITY_PROD_URL`) — the job skips cleanly.
|
|
23
23
|
*
|
|
24
|
-
* Docs: docs/
|
|
24
|
+
* Docs: docs/sync-blocks-bot.md
|
|
25
25
|
*
|
|
26
26
|
* @param bunVersion Pinned bun version, in lockstep with package.json.
|
|
27
27
|
* @param cliVersion Optional `@decocms/blocks-cli` version to run the pull
|
|
28
|
-
* with
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
*
|
|
28
|
+
* with. Omit for a freshly scaffolded site: the site's own installed copy is
|
|
29
|
+
* by definition in-version, so the local file path is used. Pass a version
|
|
30
|
+
* for a site still on an older `@decocms/*` — pinning only the sync step gets
|
|
31
|
+
* the script without bumping the runtime the site builds against (blocks-cli
|
|
32
|
+
* pins `@decocms/blocks` exactly, so bumping the devDep drags a second
|
|
33
|
+
* runtime version into the tree). Drop it when the site bumps.
|
|
34
|
+
*
|
|
35
|
+
* GOTCHA: the pinned form uses `npx --package=<pkg> <bin>`, NOT
|
|
36
|
+
* `bunx <pkg> <bin>`. `bunx pkg@ver some-bin` treats `some-bin` as an
|
|
37
|
+
* *argument* and runs the package's first bin instead — in this package that
|
|
38
|
+
* is `deco-migrate`, which rewrites the whole site and exits 0 in dry-run, so
|
|
39
|
+
* the job goes green having run the wrong tool. Observed on a real run. The
|
|
40
|
+
* `--json`-report assertion below is the second line of defence.
|
|
34
41
|
*/
|
|
35
|
-
export function
|
|
42
|
+
export function generateSyncBlocksBotYml(bunVersion: string, cliVersion?: string): string {
|
|
36
43
|
const bun = bunVersion.replace(/^bun@/, "");
|
|
37
44
|
const pullCmd = cliVersion
|
|
38
|
-
? `
|
|
39
|
-
: "bunx tsx node_modules/@decocms/blocks-cli/scripts/
|
|
40
|
-
return `name:
|
|
45
|
+
? `npx --yes --package=@decocms/blocks-cli@${cliVersion} deco-sync-blocks-bot`
|
|
46
|
+
: "bunx tsx node_modules/@decocms/blocks-cli/scripts/sync-blocks-bot.ts";
|
|
47
|
+
return `name: sync-blocks-bot
|
|
41
48
|
|
|
42
49
|
# Puxa o conteúdo publicado na loja de produção (Fresh/Deno) para \`.deco/blocks\`
|
|
43
50
|
# e abre um PR. Sem token cross-repo: o repo legado não tem permissão nenhuma
|
|
44
51
|
# aqui — este repo busca sozinho, valida e só então mergeia.
|
|
45
52
|
#
|
|
46
|
-
# Configuração (uma vez): variável de repo \`
|
|
53
|
+
# Configuração (uma vez): variável de repo \`SYNC_BLOCKS_ORIGIN\` = origin da
|
|
47
54
|
# loja de produção (ex.: https://www.minhaloja.com.br). Sem ela o job pula limpo.
|
|
48
55
|
# Para revisão humana em vez de merge automático, mude \`AUTO_MERGE\` para "false".
|
|
49
56
|
#
|
|
50
57
|
# Depois de ligar isto, APAGUE o workflow de push no repo legado e REVOGUE o
|
|
51
|
-
# token cross-repo — o pull não fecha aquela porta sozinho. Ver docs/
|
|
58
|
+
# token cross-repo — o pull não fecha aquela porta sozinho. Ver docs/sync-blocks-bot.md.
|
|
52
59
|
|
|
53
60
|
on:
|
|
54
61
|
schedule:
|
|
@@ -57,7 +64,7 @@ on:
|
|
|
57
64
|
workflow_dispatch:
|
|
58
65
|
inputs:
|
|
59
66
|
origin:
|
|
60
|
-
description: "Origin da loja de produção (sobrepõe
|
|
67
|
+
description: "Origin da loja de produção (sobrepõe SYNC_BLOCKS_ORIGIN)"
|
|
61
68
|
required: false
|
|
62
69
|
prune:
|
|
63
70
|
description: "Apagar blocos que não existem mais em produção"
|
|
@@ -75,7 +82,7 @@ permissions:
|
|
|
75
82
|
# Publicações do CMS acontecem em rajada; uma sync por vez, sem cancelar a que
|
|
76
83
|
# já está no gate de build.
|
|
77
84
|
concurrency:
|
|
78
|
-
group:
|
|
85
|
+
group: sync-blocks-bot
|
|
79
86
|
cancel-in-progress: false
|
|
80
87
|
|
|
81
88
|
env:
|
|
@@ -94,11 +101,11 @@ jobs:
|
|
|
94
101
|
id: cfg
|
|
95
102
|
env:
|
|
96
103
|
INPUT_ORIGIN: \${{ github.event.inputs.origin }}
|
|
97
|
-
VAR_ORIGIN: \${{ vars.
|
|
104
|
+
VAR_ORIGIN: \${{ vars.SYNC_BLOCKS_ORIGIN }}
|
|
98
105
|
run: |
|
|
99
106
|
origin="\${INPUT_ORIGIN:-$VAR_ORIGIN}"
|
|
100
107
|
if [ -z "$origin" ]; then
|
|
101
|
-
echo "::notice::variável de repo
|
|
108
|
+
echo "::notice::variável de repo SYNC_BLOCKS_ORIGIN não configurada — sync-blocks-bot inerte."
|
|
102
109
|
echo "skip=true" >> "$GITHUB_OUTPUT"
|
|
103
110
|
else
|
|
104
111
|
echo "origin=$origin" >> "$GITHUB_OUTPUT"
|
|
@@ -123,12 +130,22 @@ jobs:
|
|
|
123
130
|
PRUNE: \${{ github.event.inputs.prune == 'false' && ' ' || '--prune' }}
|
|
124
131
|
DRY_RUN: \${{ github.event.inputs.dry_run == 'true' && '--dry-run' || ' ' }}
|
|
125
132
|
run: |
|
|
133
|
+
# pipefail é obrigatório: sem ele o \`| tee\` mascara o exit 1 do gate
|
|
134
|
+
# de plaintext secret e o job passa mesmo tendo abortado o pull.
|
|
135
|
+
set -o pipefail
|
|
126
136
|
${pullCmd} \\
|
|
127
137
|
--origin "$ORIGIN" \\
|
|
128
138
|
--out .deco/blocks \\
|
|
129
139
|
--deny "$DENY_KEYS" \\
|
|
130
140
|
--fail-on-plaintext-secret \\
|
|
131
|
-
--github $PRUNE $DRY_RUN
|
|
141
|
+
--json --github $PRUNE $DRY_RUN | tee /tmp/sync-blocks-report.json
|
|
142
|
+
# Prova de que foi ESTE script que rodou, e que ele viu conteúdo. Sem
|
|
143
|
+
# isso, uma invocação errada que resolva para outro bin do pacote
|
|
144
|
+
# termina 0 e o job fica verde tendo rodado a ferramenta errada.
|
|
145
|
+
grep -q '"remoteBlocks"' /tmp/sync-blocks-report.json || {
|
|
146
|
+
echo "::error::o passo de pull não produziu o relatório esperado — comando errado?"
|
|
147
|
+
exit 1
|
|
148
|
+
}
|
|
132
149
|
|
|
133
150
|
- name: Guard — só .deco/blocks pode mudar
|
|
134
151
|
id: guard
|
|
@@ -137,19 +154,19 @@ jobs:
|
|
|
137
154
|
{
|
|
138
155
|
git -c core.quotepath=false diff --name-only HEAD
|
|
139
156
|
git -c core.quotepath=false ls-files --others --exclude-standard
|
|
140
|
-
} | sort -u > /tmp/
|
|
141
|
-
offending="$(grep -v '^\\.deco/blocks/' /tmp/
|
|
157
|
+
} | sort -u > /tmp/sync-blocks-changed.txt
|
|
158
|
+
offending="$(grep -v '^\\.deco/blocks/' /tmp/sync-blocks-changed.txt || true)"
|
|
142
159
|
if [ -n "$offending" ]; then
|
|
143
160
|
echo "::error::a sync mexeu fora de .deco/blocks — abortando:"
|
|
144
161
|
echo "$offending"
|
|
145
162
|
exit 1
|
|
146
163
|
fi
|
|
147
|
-
if [ ! -s /tmp/
|
|
164
|
+
if [ ! -s /tmp/sync-blocks-changed.txt ]; then
|
|
148
165
|
echo "::notice::conteúdo já está em dia, nada a sincronizar."
|
|
149
166
|
echo "changed=false" >> "$GITHUB_OUTPUT"
|
|
150
167
|
else
|
|
151
168
|
echo "changed=true" >> "$GITHUB_OUTPUT"
|
|
152
|
-
echo "$(wc -l < /tmp/
|
|
169
|
+
echo "$(wc -l < /tmp/sync-blocks-changed.txt) arquivo(s) de bloco alterado(s)"
|
|
153
170
|
fi
|
|
154
171
|
|
|
155
172
|
# Gate de verdade. Roda AQUI porque PR aberto com GITHUB_TOKEN não dispara
|
|
@@ -164,7 +181,7 @@ jobs:
|
|
|
164
181
|
GH_TOKEN: \${{ secrets.GITHUB_TOKEN }}
|
|
165
182
|
ORIGIN: \${{ steps.cfg.outputs.origin }}
|
|
166
183
|
run: |
|
|
167
|
-
branch="
|
|
184
|
+
branch="sync-blocks/$(date -u +%Y-%m-%dT%H%M%SZ)"
|
|
168
185
|
git config user.name "github-actions[bot]"
|
|
169
186
|
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
|
|
170
187
|
git checkout -b "$branch"
|
|
@@ -173,7 +190,7 @@ jobs:
|
|
|
173
190
|
git push origin "$branch"
|
|
174
191
|
url="$(gh pr create \\
|
|
175
192
|
--title "chore(content): sync .deco/blocks de produção" \\
|
|
176
|
-
--body "Conteúdo puxado de \\\`$ORIGIN/.decofile\\\` pelo workflow \\\`
|
|
193
|
+
--body "Conteúdo puxado de \\\`$ORIGIN/.decofile\\\` pelo workflow \\\`sync-blocks-bot\\\`. Só \\\`.deco/blocks/**\\\` mudou (guard) e \\\`generate + build\\\` passou antes deste PR existir." \\
|
|
177
194
|
--head "$branch")"
|
|
178
195
|
echo "PR: $url"
|
|
179
196
|
if [ "$AUTO_MERGE" = "true" ]; then
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { execFileSync } from "node:child_process";
|
|
1
2
|
import * as fs from "node:fs";
|
|
2
3
|
import * as os from "node:os";
|
|
3
4
|
import * as path from "node:path";
|
|
@@ -9,10 +10,10 @@ import {
|
|
|
9
10
|
hasEncryptedSecretRef,
|
|
10
11
|
matchesGlob,
|
|
11
12
|
writeDecofileToDir,
|
|
12
|
-
} from "./
|
|
13
|
+
} from "./sync-blocks-bot";
|
|
13
14
|
|
|
14
15
|
function tmpBlocksDir(files: Record<string, unknown> = {}): string {
|
|
15
|
-
const dir = fs.mkdtempSync(path.join(os.tmpdir(), "
|
|
16
|
+
const dir = fs.mkdtempSync(path.join(os.tmpdir(), "sync-blocks-bot-"));
|
|
16
17
|
const out = path.join(dir, ".deco", "blocks");
|
|
17
18
|
fs.mkdirSync(out, { recursive: true });
|
|
18
19
|
for (const [file, content] of Object.entries(files)) {
|
|
@@ -97,7 +98,7 @@ describe("writeDecofileToDir", () => {
|
|
|
97
98
|
});
|
|
98
99
|
|
|
99
100
|
it("compares content, not bytes — minified or reordered locals are not churn", () => {
|
|
100
|
-
const dir = fs.mkdtempSync(path.join(os.tmpdir(), "
|
|
101
|
+
const dir = fs.mkdtempSync(path.join(os.tmpdir(), "sync-blocks-bot-"));
|
|
101
102
|
const out = path.join(dir, "blocks");
|
|
102
103
|
fs.mkdirSync(out, { recursive: true });
|
|
103
104
|
// how the Studio daemon / old sync bot write it: one minified line, no newline
|
|
@@ -256,3 +257,20 @@ describe("fetchDecofile", () => {
|
|
|
256
257
|
);
|
|
257
258
|
});
|
|
258
259
|
});
|
|
260
|
+
|
|
261
|
+
describe("CLI entrypoint", () => {
|
|
262
|
+
// Regression: invoked through the package `bin`, argv[1] is the
|
|
263
|
+
// node_modules/.bin symlink while import.meta.url is the real file. A string
|
|
264
|
+
// compare of the two fails, main() never runs, and the CLI exits 0 having
|
|
265
|
+
// printed nothing — a silently green CI job. Exercise the symlink path.
|
|
266
|
+
it("runs when invoked through a bin symlink", () => {
|
|
267
|
+
const dir = fs.mkdtempSync(path.join(os.tmpdir(), "sync-blocks-bin-"));
|
|
268
|
+
const link = path.join(dir, "deco-sync-blocks-bot");
|
|
269
|
+
fs.symlinkSync(path.join(__dirname, "sync-blocks-bot.ts"), link);
|
|
270
|
+
|
|
271
|
+
const out = execFileSync(process.execPath, [require.resolve("tsx/cli"), link, "--help"], {
|
|
272
|
+
encoding: "utf-8",
|
|
273
|
+
});
|
|
274
|
+
expect(out).toContain("--fail-on-plaintext-secret");
|
|
275
|
+
});
|
|
276
|
+
});
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
* live site (`GET <origin>/.decofile`, public and unauthenticated) on a daily
|
|
12
12
|
* cron, materialises one file per block, and opens a PR. No cross-repo token,
|
|
13
13
|
* no write permission handed to anyone, and the content passes a build gate
|
|
14
|
-
* before reaching `main`. See `docs/
|
|
14
|
+
* before reaching `main`. See `docs/sync-blocks-bot.md`.
|
|
15
15
|
*
|
|
16
16
|
* Three filters decide what may be overwritten:
|
|
17
17
|
* 1. `--deny <globs>` — deny by block key (default: the `Site` block).
|
|
@@ -25,8 +25,8 @@
|
|
|
25
25
|
* committed into git.
|
|
26
26
|
*
|
|
27
27
|
* Usage (from a site root):
|
|
28
|
-
* tsx
|
|
29
|
-
* tsx
|
|
28
|
+
* tsx sync-blocks-bot.ts --origin https://www.minhaloja.com.br --prune
|
|
29
|
+
* tsx sync-blocks-bot.ts --url https://www.minhaloja.com.br/.decofile --dry-run --json
|
|
30
30
|
*
|
|
31
31
|
* Exit codes:
|
|
32
32
|
* 0 — done (with or without changes)
|
|
@@ -36,6 +36,7 @@
|
|
|
36
36
|
|
|
37
37
|
import * as fs from "node:fs";
|
|
38
38
|
import * as path from "node:path";
|
|
39
|
+
import { fileURLToPath } from "node:url";
|
|
39
40
|
import { decodeBlockNameWithPasses } from "./lib/blocks-dedupe";
|
|
40
41
|
|
|
41
42
|
/** Block keys never overwritten by a sync unless `--deny` is overridden. */
|
|
@@ -447,7 +448,7 @@ function showHelp(): void {
|
|
|
447
448
|
@decocms/blocks-cli — pull the production decofile into .deco/blocks/
|
|
448
449
|
|
|
449
450
|
Usage:
|
|
450
|
-
tsx
|
|
451
|
+
tsx sync-blocks-bot.ts --origin https://www.minhaloja.com.br [options]
|
|
451
452
|
|
|
452
453
|
Options:
|
|
453
454
|
--origin <url> Site origin; fetches <origin>/.decofile
|
|
@@ -494,7 +495,7 @@ async function main(): Promise<void> {
|
|
|
494
495
|
process.exit(0);
|
|
495
496
|
}
|
|
496
497
|
if (!opts.url) {
|
|
497
|
-
console.error("
|
|
498
|
+
console.error("sync-blocks-bot: --origin or --url is required (see --help)");
|
|
498
499
|
process.exit(2);
|
|
499
500
|
}
|
|
500
501
|
|
|
@@ -502,7 +503,7 @@ async function main(): Promise<void> {
|
|
|
502
503
|
try {
|
|
503
504
|
fetched = await fetchDecofile(opts.url, { maxBytes: opts.maxBytes, timeoutMs: opts.timeoutMs });
|
|
504
505
|
} catch (e) {
|
|
505
|
-
console.error(`
|
|
506
|
+
console.error(`sync-blocks-bot: ${(e as Error).message}`);
|
|
506
507
|
process.exit(2);
|
|
507
508
|
}
|
|
508
509
|
|
|
@@ -510,7 +511,7 @@ async function main(): Promise<void> {
|
|
|
510
511
|
try {
|
|
511
512
|
report = writeDecofileToDir(fetched.blocks, opts);
|
|
512
513
|
} catch (e) {
|
|
513
|
-
console.error(`
|
|
514
|
+
console.error(`sync-blocks-bot: ${(e as Error).message}`);
|
|
514
515
|
process.exit(2);
|
|
515
516
|
}
|
|
516
517
|
report.revision = fetched.revision;
|
|
@@ -533,25 +534,34 @@ async function main(): Promise<void> {
|
|
|
533
534
|
|
|
534
535
|
if (report.plaintextSecrets.length > 0 && opts.failOnPlaintextSecret) {
|
|
535
536
|
console.error(
|
|
536
|
-
`
|
|
537
|
+
`sync-blocks-bot: ${report.plaintextSecrets.length} plaintext credential(s) in the pulled content — refusing to commit it. Move them to encrypted secrets upstream, or deny the block with --deny.`,
|
|
537
538
|
);
|
|
538
539
|
process.exit(1);
|
|
539
540
|
}
|
|
540
541
|
process.exit(0);
|
|
541
542
|
}
|
|
542
543
|
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
544
|
+
/**
|
|
545
|
+
* Entry detection must resolve symlinks. Invoked through the package `bin`
|
|
546
|
+
* (`npx --package=@decocms/blocks-cli deco-sync-blocks-bot`), `process.argv[1]`
|
|
547
|
+
* is the `node_modules/.bin/...` symlink while `import.meta.url` is the real
|
|
548
|
+
* file — a plain string compare fails, `main()` never runs, and the CLI exits 0
|
|
549
|
+
* having printed nothing. Observed on a real CI run; the workflow's report
|
|
550
|
+
* assertion is what surfaced it.
|
|
551
|
+
*/
|
|
552
|
+
function isMainModule(): boolean {
|
|
553
|
+
if (typeof require !== "undefined" && typeof module !== "undefined" && require.main === module) {
|
|
554
|
+
return true;
|
|
555
|
+
}
|
|
556
|
+
try {
|
|
557
|
+
const arg = process.argv?.[1];
|
|
558
|
+
if (!arg) return false;
|
|
559
|
+
return fs.realpathSync(arg) === fs.realpathSync(fileURLToPath(import.meta.url));
|
|
560
|
+
} catch {
|
|
561
|
+
return false;
|
|
562
|
+
}
|
|
554
563
|
}
|
|
555
|
-
|
|
564
|
+
|
|
565
|
+
if (isMainModule()) {
|
|
556
566
|
void main();
|
|
557
567
|
}
|