social-autoposter 1.6.85 → 1.6.86
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/mcp/dist/index.js +1 -8
- package/mcp/dist/repo.js +0 -63
- package/mcp/dist/telemetry.js +1 -127
- package/mcp/dist/version.json +2 -2
- package/mcp/manifest.json +1 -1
- package/mcp/package.json +1 -1
- package/package.json +1 -1
- package/scripts/release-mcpb.sh +109 -20
package/mcp/dist/index.js
CHANGED
|
@@ -22,7 +22,7 @@ import { xStatus, xConnect, xDetectSources, xScanProfile, summarizeXAuth } from
|
|
|
22
22
|
import { startProvisioning, isProvisioning, readProgress, runtimeReady, readRuntime, resolvePython, resolveChrome, ensureMenubar, ensurePipelineCurrent, } from "./runtime.js";
|
|
23
23
|
import { blockOnboardingMilestone, completeOnboardingMilestone, ensureDoctorPhase, onboardingLedger, onboardingSnapshot, recordOnboardingAttempt, runDoctorPhase, } from "./onboarding.js";
|
|
24
24
|
import { VERSION, versionStatus, latestPublishedVersion } from "./version.js";
|
|
25
|
-
import { initSentry, sendHeartbeat, captureError, flushSentry
|
|
25
|
+
import { initSentry, sendHeartbeat, captureError, flushSentry } from "./telemetry.js";
|
|
26
26
|
import { registerAppTool, registerAppResource, RESOURCE_MIME_TYPE, getUiCapability, } from "@modelcontextprotocol/ext-apps/server";
|
|
27
27
|
import { fileURLToPath } from "node:url";
|
|
28
28
|
import http from "node:http";
|
|
@@ -616,7 +616,6 @@ async function postApproved(batchId, plan) {
|
|
|
616
616
|
});
|
|
617
617
|
void flushSentry(2000);
|
|
618
618
|
}
|
|
619
|
-
void flushLogs();
|
|
620
619
|
return {
|
|
621
620
|
attempted: approved.length,
|
|
622
621
|
posted: realPosted,
|
|
@@ -2384,11 +2383,6 @@ registerAppResource(server, "S4L product link", PRODUCT_LINK_URI, { mimeType: RE
|
|
|
2384
2383
|
}));
|
|
2385
2384
|
async function main() {
|
|
2386
2385
|
initSentry();
|
|
2387
|
-
// Tee the verbatim stdout/stderr of every pipeline subprocess to the s4l
|
|
2388
|
-
// backend so we can troubleshoot/rescue any user scenario (silent stalls,
|
|
2389
|
-
// partial onboarding) without asking them to ship a log file. Best-effort;
|
|
2390
|
-
// disabled with SAPS_LOG_STREAM=0.
|
|
2391
|
-
startLogStreaming();
|
|
2392
2386
|
// A plugin UPDATE refreshes this server (dist/) but not the materialized
|
|
2393
2387
|
// pipeline. Re-extract the bundled pipeline.tgz when it's newer than what's on
|
|
2394
2388
|
// disk, BEFORE serving, so the very first scan uses the shipped pipeline (not
|
|
@@ -2439,7 +2433,6 @@ async function main() {
|
|
|
2439
2433
|
main().catch(async (err) => {
|
|
2440
2434
|
console.error("[social-autoposter-mcp] fatal:", err);
|
|
2441
2435
|
captureError(err, { component: "main" });
|
|
2442
|
-
await flushLogs();
|
|
2443
2436
|
await flushSentry();
|
|
2444
2437
|
process.exit(1);
|
|
2445
2438
|
});
|
package/mcp/dist/repo.js
CHANGED
|
@@ -30,27 +30,6 @@ export function repoDir() {
|
|
|
30
30
|
// "No drafts in batch ...". Default to /tmp to match the script; allow an explicit
|
|
31
31
|
// override for non-standard installs.
|
|
32
32
|
export const TMP_DIR = process.env.SAPS_TMP_DIR || "/tmp";
|
|
33
|
-
let lineSink = null;
|
|
34
|
-
export function setLineSink(fn) {
|
|
35
|
-
lineSink = fn;
|
|
36
|
-
}
|
|
37
|
-
// Derive a short, stable context label for a spawned command so log lines can
|
|
38
|
-
// be grouped by which script produced them (e.g. "scripts/seed_search_topics.py",
|
|
39
|
-
// "skill/run-twitter-cycle.sh"). Best-effort; never throws.
|
|
40
|
-
function deriveContext(cmd, args) {
|
|
41
|
-
try {
|
|
42
|
-
const base = cmd.split("/").pop() || cmd;
|
|
43
|
-
if (/python/i.test(base) || base === "bash" || base === "sh" || base === "node") {
|
|
44
|
-
const first = args.find((a) => !a.startsWith("-"));
|
|
45
|
-
if (first)
|
|
46
|
-
return first;
|
|
47
|
-
}
|
|
48
|
-
return [base, args[0] ?? ""].join(" ").trim();
|
|
49
|
-
}
|
|
50
|
-
catch {
|
|
51
|
-
return cmd;
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
33
|
// Spawn a process inside the repo, inheriting the repo env (API base + keys
|
|
55
34
|
// come from the install's environment / .env loaded by the scripts themselves).
|
|
56
35
|
//
|
|
@@ -94,29 +73,6 @@ export function run(cmd, args, opts = {}) {
|
|
|
94
73
|
}
|
|
95
74
|
return buf;
|
|
96
75
|
};
|
|
97
|
-
// Parallel whole-line splitter that tees to the telemetry sink (if any),
|
|
98
|
-
// kept separate from the onLine pump so neither path can affect the other.
|
|
99
|
-
const logCtx = opts.logContext || deriveContext(cmd, args);
|
|
100
|
-
let sinkOutBuf = "";
|
|
101
|
-
let sinkErrBuf = "";
|
|
102
|
-
const sinkPump = (chunk, which, buf) => {
|
|
103
|
-
const sink = lineSink;
|
|
104
|
-
if (!sink)
|
|
105
|
-
return buf;
|
|
106
|
-
buf += chunk;
|
|
107
|
-
let nl;
|
|
108
|
-
while ((nl = buf.indexOf("\n")) !== -1) {
|
|
109
|
-
const line = buf.slice(0, nl);
|
|
110
|
-
buf = buf.slice(nl + 1);
|
|
111
|
-
try {
|
|
112
|
-
sink(line, which, logCtx);
|
|
113
|
-
}
|
|
114
|
-
catch {
|
|
115
|
-
/* the telemetry sink must never break the wrapped command */
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
return buf;
|
|
119
|
-
};
|
|
120
76
|
let timer;
|
|
121
77
|
if (opts.timeoutMs) {
|
|
122
78
|
timer = setTimeout(() => {
|
|
@@ -127,13 +83,11 @@ export function run(cmd, args, opts = {}) {
|
|
|
127
83
|
const s = d.toString();
|
|
128
84
|
stdout += s;
|
|
129
85
|
outBuf = pump(s, "stdout", outBuf);
|
|
130
|
-
sinkOutBuf = sinkPump(s, "stdout", sinkOutBuf);
|
|
131
86
|
});
|
|
132
87
|
child.stderr.on("data", (d) => {
|
|
133
88
|
const s = d.toString();
|
|
134
89
|
stderr += s;
|
|
135
90
|
errBuf = pump(s, "stderr", errBuf);
|
|
136
|
-
sinkErrBuf = sinkPump(s, "stderr", sinkErrBuf);
|
|
137
91
|
});
|
|
138
92
|
child.on("close", (code) => {
|
|
139
93
|
if (timer)
|
|
@@ -155,23 +109,6 @@ export function run(cmd, args, opts = {}) {
|
|
|
155
109
|
/* ignore */
|
|
156
110
|
}
|
|
157
111
|
}
|
|
158
|
-
const sink = lineSink;
|
|
159
|
-
if (sink) {
|
|
160
|
-
if (sinkOutBuf)
|
|
161
|
-
try {
|
|
162
|
-
sink(sinkOutBuf, "stdout", logCtx);
|
|
163
|
-
}
|
|
164
|
-
catch {
|
|
165
|
-
/* ignore */
|
|
166
|
-
}
|
|
167
|
-
if (sinkErrBuf)
|
|
168
|
-
try {
|
|
169
|
-
sink(sinkErrBuf, "stderr", logCtx);
|
|
170
|
-
}
|
|
171
|
-
catch {
|
|
172
|
-
/* ignore */
|
|
173
|
-
}
|
|
174
|
-
}
|
|
175
112
|
resolve({ code: code ?? -1, stdout, stderr });
|
|
176
113
|
});
|
|
177
114
|
child.on("error", (err) => {
|
package/mcp/dist/telemetry.js
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
import * as Sentry from "@sentry/node";
|
|
10
10
|
import path from "node:path";
|
|
11
11
|
import fs from "node:fs";
|
|
12
|
-
import { repoDir, runPython
|
|
12
|
+
import { repoDir, runPython } from "./repo.js";
|
|
13
13
|
import { VERSION } from "./version.js";
|
|
14
14
|
// Sentry DSN is a client-side identifier (safe to embed, same posture as Fazm's
|
|
15
15
|
// hardcoded Swift DSN). Overridable via env for dev. Empty -> Sentry disabled.
|
|
@@ -102,129 +102,3 @@ export async function sendHeartbeat(reason) {
|
|
|
102
102
|
console.error("[social-autoposter-mcp] heartbeat failed:", err?.message || err);
|
|
103
103
|
}
|
|
104
104
|
}
|
|
105
|
-
// ---- Raw subprocess log streaming ------------------------------------------
|
|
106
|
-
// Tees the verbatim stdout/stderr of every pipeline subprocess (via the
|
|
107
|
-
// repo.ts run() boundary) to the s4l backend, so we can troubleshoot and
|
|
108
|
-
// rescue any user scenario without asking them to ship a log file. Lines are
|
|
109
|
-
// buffered in memory and flushed in small batches under the same X-Installation
|
|
110
|
-
// identity the heartbeat uses. Best-effort: this NEVER throws into the server,
|
|
111
|
-
// never blocks the child's I/O, and drops on overflow rather than growing
|
|
112
|
-
// unbounded. Disable with SAPS_LOG_STREAM=0.
|
|
113
|
-
const LOG_STREAM_ENABLED = process.env.SAPS_LOG_STREAM !== "0";
|
|
114
|
-
const LOG_MAX_LINE_LEN = 8192; // mirror the backend cap
|
|
115
|
-
const LOG_MAX_BUFFER = 1000; // drop oldest beyond this (overflow protection)
|
|
116
|
-
const LOG_FLUSH_BATCH = 100; // flush eagerly once we have this many lines
|
|
117
|
-
const LOG_MAX_PER_POST = 200; // backend accepts 1-200 per request
|
|
118
|
-
const LOG_FLUSH_MS = 3000; // otherwise flush on this cadence
|
|
119
|
-
const logBuffer = [];
|
|
120
|
-
let logDropped = 0; // count of lines dropped on overflow (surfaced periodically)
|
|
121
|
-
let logFlushing = false;
|
|
122
|
-
let logTimer;
|
|
123
|
-
let cachedInstallHeader = null;
|
|
124
|
-
let logStreamingStarted = false;
|
|
125
|
-
async function installHeader() {
|
|
126
|
-
if (cachedInstallHeader)
|
|
127
|
-
return cachedInstallHeader;
|
|
128
|
-
try {
|
|
129
|
-
const idScript = path.join(repoDir(), "scripts", "identity.py");
|
|
130
|
-
if (!fs.existsSync(idScript))
|
|
131
|
-
return null;
|
|
132
|
-
const res = await runPython("scripts/identity.py", ["header"], { timeoutMs: 10_000 });
|
|
133
|
-
const header = (res.stdout || "").trim();
|
|
134
|
-
if (res.code === 0 && header) {
|
|
135
|
-
cachedInstallHeader = header;
|
|
136
|
-
return header;
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
catch {
|
|
140
|
-
/* best-effort */
|
|
141
|
-
}
|
|
142
|
-
return null;
|
|
143
|
-
}
|
|
144
|
-
// Buffer one raw line. Called from the repo.ts line sink, so it must be cheap
|
|
145
|
-
// and total non-throwing.
|
|
146
|
-
export function logLine(stream, line, context) {
|
|
147
|
-
if (!LOG_STREAM_ENABLED)
|
|
148
|
-
return;
|
|
149
|
-
try {
|
|
150
|
-
logBuffer.push({
|
|
151
|
-
ts: new Date().toISOString(),
|
|
152
|
-
stream,
|
|
153
|
-
line: line.length > LOG_MAX_LINE_LEN ? line.slice(0, LOG_MAX_LINE_LEN) : line,
|
|
154
|
-
context: context || "",
|
|
155
|
-
});
|
|
156
|
-
if (logBuffer.length > LOG_MAX_BUFFER) {
|
|
157
|
-
// Drop oldest to bound memory; the newest lines are the most useful.
|
|
158
|
-
logDropped += logBuffer.length - LOG_MAX_BUFFER;
|
|
159
|
-
logBuffer.splice(0, logBuffer.length - LOG_MAX_BUFFER);
|
|
160
|
-
}
|
|
161
|
-
if (logBuffer.length >= LOG_FLUSH_BATCH)
|
|
162
|
-
void flushLogs();
|
|
163
|
-
}
|
|
164
|
-
catch {
|
|
165
|
-
/* never throw into the run() boundary */
|
|
166
|
-
}
|
|
167
|
-
}
|
|
168
|
-
export async function flushLogs() {
|
|
169
|
-
if (!LOG_STREAM_ENABLED)
|
|
170
|
-
return;
|
|
171
|
-
if (logFlushing || logBuffer.length === 0)
|
|
172
|
-
return;
|
|
173
|
-
logFlushing = true;
|
|
174
|
-
try {
|
|
175
|
-
const header = await installHeader();
|
|
176
|
-
if (!header)
|
|
177
|
-
return; // runtime not unpacked yet; keep buffering
|
|
178
|
-
const base = (process.env.AUTOPOSTER_API_BASE || "https://s4l.ai").replace(/\/+$/, "");
|
|
179
|
-
// Drain in <=200-line POSTs until the buffer empties (or a POST fails).
|
|
180
|
-
while (logBuffer.length > 0) {
|
|
181
|
-
const batch = logBuffer.splice(0, LOG_MAX_PER_POST);
|
|
182
|
-
const lines = batch.map((b) => ({
|
|
183
|
-
ts: b.ts,
|
|
184
|
-
stream: b.stream,
|
|
185
|
-
line: b.line,
|
|
186
|
-
context: b.context || undefined,
|
|
187
|
-
}));
|
|
188
|
-
try {
|
|
189
|
-
const resp = await fetch(`${base}/api/v1/installations/logs`, {
|
|
190
|
-
method: "POST",
|
|
191
|
-
headers: { "X-Installation": header, "content-type": "application/json" },
|
|
192
|
-
body: JSON.stringify({ lines }),
|
|
193
|
-
signal: AbortSignal.timeout(15_000),
|
|
194
|
-
});
|
|
195
|
-
if (!resp.ok) {
|
|
196
|
-
// Drop this batch (don't re-buffer): a persistent 4xx/5xx would grow
|
|
197
|
-
// the buffer unbounded. The raw stream is best-effort.
|
|
198
|
-
console.error(`[social-autoposter-mcp] log flush http ${resp.status}`);
|
|
199
|
-
break;
|
|
200
|
-
}
|
|
201
|
-
}
|
|
202
|
-
catch (err) {
|
|
203
|
-
// Network blip: drop this batch, stop draining, try again next tick.
|
|
204
|
-
console.error("[social-autoposter-mcp] log flush failed:", err?.message || err);
|
|
205
|
-
break;
|
|
206
|
-
}
|
|
207
|
-
}
|
|
208
|
-
if (logDropped > 0) {
|
|
209
|
-
console.error(`[social-autoposter-mcp] log stream dropped ${logDropped} line(s) on overflow`);
|
|
210
|
-
logDropped = 0;
|
|
211
|
-
}
|
|
212
|
-
}
|
|
213
|
-
finally {
|
|
214
|
-
logFlushing = false;
|
|
215
|
-
}
|
|
216
|
-
}
|
|
217
|
-
// Register the repo.ts line sink and start the periodic flush. Idempotent.
|
|
218
|
-
export function startLogStreaming() {
|
|
219
|
-
if (!LOG_STREAM_ENABLED || logStreamingStarted)
|
|
220
|
-
return;
|
|
221
|
-
logStreamingStarted = true;
|
|
222
|
-
try {
|
|
223
|
-
setLineSink((line, stream, context) => logLine(stream, line, context));
|
|
224
|
-
logTimer = setInterval(() => void flushLogs(), LOG_FLUSH_MS);
|
|
225
|
-
logTimer.unref();
|
|
226
|
-
}
|
|
227
|
-
catch (err) {
|
|
228
|
-
console.error("[social-autoposter-mcp] log streaming start failed:", err?.message || err);
|
|
229
|
-
}
|
|
230
|
-
}
|
package/mcp/dist/version.json
CHANGED
package/mcp/manifest.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"dxt_version": "0.1",
|
|
3
3
|
"name": "social-autoposter",
|
|
4
4
|
"display_name": "S4L",
|
|
5
|
-
"version": "1.6.
|
|
5
|
+
"version": "1.6.86",
|
|
6
6
|
"description": "Draft, review, approve, and autopilot X/Twitter posts. Thin desktop client over the S4L pipeline.",
|
|
7
7
|
"long_description": "A guided assistant that drafts, reviews, and autopilots X/Twitter posts.\nTo get started:\n1. Click **Configure** and set every tool permission to **Always Allow**.\n2. Copy this prompt: **Set me up on S4L end to end**.\n3. Quit fully with CMD+Q, restart Claude, and paste the prompt into a new chat.",
|
|
8
8
|
"author": {
|
package/mcp/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@m13v/social-autoposter-mcp",
|
|
3
|
-
"version": "1.6.
|
|
3
|
+
"version": "1.6.86",
|
|
4
4
|
"private": true,
|
|
5
5
|
"description": "Desktop MCP client for social-autoposter (X/Twitter rail): manual draft/review/approve loop, autopilot control, and stats. Thin wrapper over the existing pipeline scripts.",
|
|
6
6
|
"license": "MIT",
|
package/package.json
CHANGED
package/scripts/release-mcpb.sh
CHANGED
|
@@ -1,21 +1,35 @@
|
|
|
1
1
|
#!/usr/bin/env bash
|
|
2
|
-
#
|
|
2
|
+
# THE single release flow for social-autoposter. One command does EVERYTHING and
|
|
3
|
+
# keeps the npm path (Story A) and the .mcpb double-click path (Story B) on one
|
|
4
|
+
# version, because both derive from one bumped repo-root package.json.
|
|
3
5
|
#
|
|
4
6
|
# What it does, end to end:
|
|
5
|
-
# 1.
|
|
6
|
-
#
|
|
7
|
-
#
|
|
7
|
+
# 1. Bump the repo-root package.json (the SINGLE source of truth) and lockfile.
|
|
8
|
+
# Default: patch bump. --bump minor|major, or pin with --version / --tag,
|
|
9
|
+
# or --no-bump to re-release the current version as-is.
|
|
10
|
+
# 2. Build the MCP bundle: vite (panel) + tsc (server) + bundle-pipeline. The
|
|
11
|
+
# embedded pipeline.tgz is `npm pack` of the (now-bumped) repo, so the shell
|
|
12
|
+
# and the bundled Python pipeline CANNOT diverge.
|
|
13
|
+
# 3. Stamp dist/version.json + manifest.json + mcp/package.json(+lock) to match.
|
|
8
14
|
# 4. Pack mcp/ into mcp/social-autoposter.mcpb via the mcpb CLI.
|
|
9
|
-
# 5. Verify
|
|
10
|
-
#
|
|
15
|
+
# 5. Verify: size cap, embedded pipeline.tgz present, version.json + manifest +
|
|
16
|
+
# the pipeline.tgz's OWN internal version all == VERSION (the guard that was
|
|
17
|
+
# missing when 1.6.84 shipped a 1.6.83 pipeline), install tools present.
|
|
18
|
+
# 6. npm publish social-autoposter@VERSION (idempotent; skipped if already live).
|
|
19
|
+
# 7. Create/update GitHub release vX.Y.Z and upload the .mcpb (--clobber).
|
|
11
20
|
#
|
|
12
|
-
#
|
|
21
|
+
# Boxes self-update from the GitHub release: the menu-bar updater polls
|
|
22
|
+
# releases/latest, pulls the new .mcpb, and on next server boot
|
|
23
|
+
# ensurePipelineCurrent() re-extracts pipeline.tgz. No manual box step needed.
|
|
13
24
|
#
|
|
14
25
|
# Usage:
|
|
15
|
-
# bash scripts/release-mcpb.sh
|
|
16
|
-
# bash scripts/release-mcpb.sh --
|
|
17
|
-
# bash scripts/release-mcpb.sh --
|
|
18
|
-
# bash scripts/release-mcpb.sh --
|
|
26
|
+
# bash scripts/release-mcpb.sh # patch bump, npm + .mcpb + GitHub
|
|
27
|
+
# bash scripts/release-mcpb.sh --bump minor
|
|
28
|
+
# bash scripts/release-mcpb.sh --version 1.7.0 # pin an exact version
|
|
29
|
+
# bash scripts/release-mcpb.sh --no-bump # re-release current package.json version
|
|
30
|
+
# bash scripts/release-mcpb.sh --no-npm # skip npm publish (only .mcpb + GitHub)
|
|
31
|
+
# bash scripts/release-mcpb.sh --no-release # build + pack + verify only (no npm, no GitHub)
|
|
32
|
+
# bash scripts/release-mcpb.sh --draft # GitHub release as a draft
|
|
19
33
|
|
|
20
34
|
set -euo pipefail
|
|
21
35
|
|
|
@@ -29,38 +43,82 @@ GH_REPO="m13v/social-autoposter"
|
|
|
29
43
|
SIZE_CAP_MB=180
|
|
30
44
|
|
|
31
45
|
TAG_OVERRIDE=""
|
|
46
|
+
VERSION_OVERRIDE=""
|
|
32
47
|
DO_RELEASE=1
|
|
48
|
+
DO_NPM=1
|
|
49
|
+
DO_BUMP=1
|
|
50
|
+
BUMP_LEVEL="patch"
|
|
33
51
|
DRAFT_FLAG=""
|
|
34
52
|
|
|
35
53
|
while [[ $# -gt 0 ]]; do
|
|
36
54
|
case "$1" in
|
|
37
55
|
--tag) TAG_OVERRIDE="$2"; shift 2 ;;
|
|
38
56
|
--tag=*) TAG_OVERRIDE="${1#*=}"; shift ;;
|
|
57
|
+
--version) VERSION_OVERRIDE="$2"; shift 2 ;;
|
|
58
|
+
--version=*) VERSION_OVERRIDE="${1#*=}"; shift ;;
|
|
59
|
+
--bump) BUMP_LEVEL="$2"; shift 2 ;;
|
|
60
|
+
--bump=*) BUMP_LEVEL="${1#*=}"; shift ;;
|
|
61
|
+
--no-bump) DO_BUMP=0; shift ;;
|
|
62
|
+
--no-npm) DO_NPM=0; shift ;;
|
|
39
63
|
--no-release) DO_RELEASE=0; shift ;;
|
|
40
64
|
--draft) DRAFT_FLAG="--draft"; shift ;;
|
|
41
|
-
-h|--help) sed -n '2,
|
|
65
|
+
-h|--help) sed -n '2,38p' "$0"; exit 0 ;;
|
|
42
66
|
*) echo "unknown arg: $1" >&2; exit 2 ;;
|
|
43
67
|
esac
|
|
44
68
|
done
|
|
45
69
|
|
|
70
|
+
case "$BUMP_LEVEL" in
|
|
71
|
+
patch|minor|major) ;;
|
|
72
|
+
*) echo "invalid --bump level: $BUMP_LEVEL (want patch|minor|major)" >&2; exit 2 ;;
|
|
73
|
+
esac
|
|
74
|
+
|
|
46
75
|
say() { printf '\n\033[1m==> %s\033[0m\n' "$*"; }
|
|
47
76
|
die() { printf '\033[1mERROR:\033[0m %s\n' "$*" >&2; exit 1; }
|
|
48
77
|
|
|
49
78
|
command -v node >/dev/null || die "node not found on PATH"
|
|
50
79
|
command -v mcpb >/dev/null || die "mcpb CLI not found (npm i -g @anthropic-ai/mcpb)"
|
|
51
80
|
|
|
52
|
-
# ---- 1. Resolve version
|
|
81
|
+
# ---- 1. Resolve + WRITE version into the repo-root package.json -------------
|
|
82
|
+
# The repo-root package.json is the SINGLE source of truth: `npm pack` reads it
|
|
83
|
+
# to build the embedded pipeline.tgz, so the bundle shell and the bundled
|
|
84
|
+
# pipeline cannot diverge as long as we bump it BEFORE building (step 2). This
|
|
85
|
+
# closes the 1.6.84-class bug where the shell said X but pipeline.tgz carried
|
|
86
|
+
# the prior version because only the satellites were stamped.
|
|
53
87
|
PKG_VERSION="$(node -p "require('$REPO_ROOT/package.json').version")"
|
|
54
|
-
if [[ -n "$
|
|
55
|
-
|
|
56
|
-
|
|
88
|
+
if [[ -n "$VERSION_OVERRIDE" ]]; then
|
|
89
|
+
VERSION="${VERSION_OVERRIDE#v}"
|
|
90
|
+
elif [[ -n "$TAG_OVERRIDE" ]]; then
|
|
91
|
+
VERSION="${TAG_OVERRIDE#v}"
|
|
92
|
+
elif [[ "$DO_BUMP" == "1" ]]; then
|
|
93
|
+
# Compute the next version without writing a git tag; we own the write below.
|
|
94
|
+
VERSION="$(node -e "
|
|
95
|
+
let [a,b,c]='$PKG_VERSION'.split('.').map(Number);
|
|
96
|
+
const lvl='$BUMP_LEVEL';
|
|
97
|
+
if(lvl==='major'){a++;b=0;c=0;} else if(lvl==='minor'){b++;c=0;} else {c++;}
|
|
98
|
+
console.log(a+'.'+b+'.'+c);
|
|
99
|
+
")"
|
|
57
100
|
else
|
|
58
101
|
VERSION="$PKG_VERSION"
|
|
59
|
-
TAG="v$VERSION"
|
|
60
102
|
fi
|
|
61
|
-
|
|
103
|
+
TAG="v$VERSION"
|
|
104
|
+
|
|
105
|
+
# Write the resolved version into the repo-root package.json + lockfile so the
|
|
106
|
+
# pipeline tarball, npm publish, and all satellites share one number.
|
|
62
107
|
if [[ "$VERSION" != "$PKG_VERSION" ]]; then
|
|
63
|
-
|
|
108
|
+
say "Bumping repo-root package.json $PKG_VERSION -> $VERSION"
|
|
109
|
+
node -e "
|
|
110
|
+
const fs=require('fs');
|
|
111
|
+
for (const p of ['$REPO_ROOT/package.json','$REPO_ROOT/package-lock.json']) {
|
|
112
|
+
if (!fs.existsSync(p)) continue;
|
|
113
|
+
const j=JSON.parse(fs.readFileSync(p,'utf8'));
|
|
114
|
+
j.version='$VERSION';
|
|
115
|
+
if (j.packages && j.packages['']) j.packages[''].version='$VERSION';
|
|
116
|
+
fs.writeFileSync(p, JSON.stringify(j,null,2)+'\n');
|
|
117
|
+
console.log(' '+p.replace('$REPO_ROOT/','')+' -> '+j.version);
|
|
118
|
+
}
|
|
119
|
+
"
|
|
120
|
+
else
|
|
121
|
+
say "Releasing $TAG (repo-root package.json already $VERSION)"
|
|
64
122
|
fi
|
|
65
123
|
|
|
66
124
|
# ---- 2. Build the bundle ----------------------------------------------------
|
|
@@ -128,6 +186,14 @@ MANIFEST_VER=$(unzip -p "$BUNDLE" manifest.json 2>/dev/null | node -p "JSON.pars
|
|
|
128
186
|
[[ "$MANIFEST_VER" == "$VERSION" ]] || die "bundle manifest.json=$MANIFEST_VER != $VERSION (Desktop Details panel would show the wrong version)"
|
|
129
187
|
echo " manifest.json: $MANIFEST_VER ok"
|
|
130
188
|
|
|
189
|
+
# THE guard that was missing when 1.6.84 shipped a 1.6.83 pipeline: assert the
|
|
190
|
+
# version INSIDE the embedded pipeline.tgz matches the bundle. ensurePipelineCurrent()
|
|
191
|
+
# on the box trusts version.json to decide whether to re-extract; if the tarball's
|
|
192
|
+
# own package.json lags, the box materializes stale Python and never knows.
|
|
193
|
+
PIPELINE_VER=$(unzip -p "$BUNDLE" dist/pipeline.tgz 2>/dev/null | tar -xzO package/package.json 2>/dev/null | node -p "JSON.parse(require('fs').readFileSync(0,'utf8')).version" 2>/dev/null || echo "?")
|
|
194
|
+
[[ "$PIPELINE_VER" == "$VERSION" ]] || die "embedded pipeline.tgz version=$PIPELINE_VER != $VERSION (box would materialize a STALE pipeline; bump repo-root package.json BEFORE build)"
|
|
195
|
+
echo " pipeline.tgz internal version: $PIPELINE_VER ok"
|
|
196
|
+
|
|
131
197
|
for f in "dist/index.js" "dist/runtime.js" "manifest.json"; do
|
|
132
198
|
# grep -c reads all input (no SIGPIPE); anchor on the time column + 3-space
|
|
133
199
|
# gutter so node_modules/.../dist/index.js does not false-match the top-level.
|
|
@@ -141,7 +207,30 @@ if [[ "$DO_RELEASE" == "0" ]]; then
|
|
|
141
207
|
exit 0
|
|
142
208
|
fi
|
|
143
209
|
|
|
144
|
-
# ---- 6.
|
|
210
|
+
# ---- 6. npm publish (Story A: `npx social-autoposter@<v> init`) -------------
|
|
211
|
+
# Same VERSION as the bundle, from the SAME bumped repo-root package.json, so the
|
|
212
|
+
# npm install path and the .mcpb path can never disagree. Idempotent: a version
|
|
213
|
+
# already on the registry is skipped, not failed.
|
|
214
|
+
if [[ "$DO_NPM" == "1" ]]; then
|
|
215
|
+
command -v npm >/dev/null || die "npm not found on PATH"
|
|
216
|
+
NPM_HTTP=$(curl -s -o /dev/null -w "%{http_code}" "https://registry.npmjs.org/social-autoposter/$VERSION" || echo "000")
|
|
217
|
+
if [[ "$NPM_HTTP" == "200" ]]; then
|
|
218
|
+
say "npm: social-autoposter@$VERSION already published — skipping"
|
|
219
|
+
else
|
|
220
|
+
say "Publishing social-autoposter@$VERSION to npm"
|
|
221
|
+
( cd "$REPO_ROOT" && npm publish ) || die "npm publish failed"
|
|
222
|
+
# Confirm it actually landed (granular-token whoami lies; a version fetch doesn't).
|
|
223
|
+
for _ in 1 2 3 4 5; do
|
|
224
|
+
sleep 2
|
|
225
|
+
[[ "$(curl -s -o /dev/null -w '%{http_code}' "https://registry.npmjs.org/social-autoposter/$VERSION")" == "200" ]] && break
|
|
226
|
+
done
|
|
227
|
+
echo " npm: social-autoposter@$VERSION live"
|
|
228
|
+
fi
|
|
229
|
+
else
|
|
230
|
+
say "npm publish skipped (--no-npm)"
|
|
231
|
+
fi
|
|
232
|
+
|
|
233
|
+
# ---- 7. GitHub release ------------------------------------------------------
|
|
145
234
|
command -v gh >/dev/null || die "gh CLI not found"
|
|
146
235
|
gh auth status >/dev/null 2>&1 || die "gh not authenticated (run: gh auth login)"
|
|
147
236
|
|