@nodegx/mcp 0.2.6 → 0.2.8
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/README.md +16 -11
- package/dist/backend/cli.js +5801 -2527
- package/dist/backend/folder/start.js +89 -1
- package/dist/backend/vm/provision-app.sh +41 -0
- package/dist/cloud-bundle.cjs +4265 -1666
- package/dist/kit-extract.cjs +4742 -2156
- package/dist/kit-extract.inputs.json +31 -1
- package/dist/node-catalog.json +10 -9
- package/dist/noodl-mcp.cjs +12019 -7425
- package/dist/package.json +1 -1
- package/dist/render-harness/data/node-catalog-enriched.json +1353 -254
- package/dist/render-harness/data/node-catalog.json +10 -9
- package/dist/render-harness/drive-steps.js +271 -40
- package/dist/render-harness/measure-from-disk.js +174 -22
- package/dist/src/external/viewer/noodl.viewer.js +1 -1
- package/package.json +1 -1
|
@@ -32,6 +32,11 @@
|
|
|
32
32
|
* (`security.json.before-<time>`) and the backend installs the project's. A restart, or a deploy
|
|
33
33
|
* with the same rules, leaves `data/security.json` as it is (Richard, 2026-10-23: a reship
|
|
34
34
|
* brings the project's rules; an edit made on the server lasts until the project's change).
|
|
35
|
+
* 4b. Installs the project's workflows and triggers (`backend/nodegx.automations.json`, P127
|
|
36
|
+
* BKO-001) by the same rule: on a first start, and after a deploy that changed them, the
|
|
37
|
+
* backend's `data/triggers.json` and `data/workflow-defs/` are moved aside (`.before-<time>`) and
|
|
38
|
+
* the backend loads the project's. Webhook secrets stay in `data/secrets.json`, so a sender's
|
|
39
|
+
* secret survives a reship. A restart, or a deploy with the same ones, leaves them as they are.
|
|
35
40
|
* 5. Refuses rules that are switched off (`"devOpen": true`), which is how a development backend's
|
|
36
41
|
* data arrives — and the backend's own interlock only refuses that on a public bind, while this
|
|
37
42
|
* app is usually behind a proxy on 127.0.0.1. Refused before anything in the data is changed.
|
|
@@ -58,6 +63,13 @@ const BACKEND_RULES = 'security.json';
|
|
|
58
63
|
/** Which rules the last deploy installed (their checksum), in the data folder. */
|
|
59
64
|
const RULES_MARKER = 'deployed-rules.json';
|
|
60
65
|
const WORKFLOW_SUFFIX = '.workflow.json';
|
|
66
|
+
/** The project's workflows and triggers (P127 BKO-001). */
|
|
67
|
+
const AUTOMATIONS_FILE = 'nodegx.automations.json';
|
|
68
|
+
/** The backend's own copies of them, in the data folder. */
|
|
69
|
+
const BACKEND_TRIGGERS = 'triggers.json';
|
|
70
|
+
const BACKEND_WORKFLOWS = 'workflow-defs';
|
|
71
|
+
/** Which automations the last deploy installed (their checksum), in the data folder. */
|
|
72
|
+
const AUTOMATIONS_MARKER = 'deployed-automations.json';
|
|
61
73
|
|
|
62
74
|
/** The Node this app needs, and the one property that decides it. */
|
|
63
75
|
const NODE_NEEDED = '22.13';
|
|
@@ -240,6 +252,74 @@ function planRules(dataDir) {
|
|
|
240
252
|
return { action, current, shipped, config, sha256 };
|
|
241
253
|
}
|
|
242
254
|
|
|
255
|
+
/**
|
|
256
|
+
* What to do with the project's workflows and triggers: the same four answers as `planRules`. The
|
|
257
|
+
* backend has some when it has a `triggers.json` or any workflow definition.
|
|
258
|
+
*/
|
|
259
|
+
function planAutomations(dataDir) {
|
|
260
|
+
const shipped = path.join(BACKEND, AUTOMATIONS_FILE);
|
|
261
|
+
const triggers = path.join(dataDir, BACKEND_TRIGGERS);
|
|
262
|
+
const workflows = path.join(dataDir, BACKEND_WORKFLOWS);
|
|
263
|
+
if (!fs.existsSync(shipped)) return { action: 'none', triggers, workflows };
|
|
264
|
+
const bytes = fs.readFileSync(shipped);
|
|
265
|
+
try {
|
|
266
|
+
JSON.parse(bytes.toString('utf8'));
|
|
267
|
+
} catch (error) {
|
|
268
|
+
fail(
|
|
269
|
+
`This deploy's workflows and triggers (backend/${AUTOMATIONS_FILE}) are not valid JSON: ${error.message}. Deploy the project again.`
|
|
270
|
+
);
|
|
271
|
+
}
|
|
272
|
+
const sha256 = crypto.createHash('sha256').update(bytes).digest('hex');
|
|
273
|
+
let installed = null;
|
|
274
|
+
try {
|
|
275
|
+
installed = JSON.parse(fs.readFileSync(path.join(dataDir, AUTOMATIONS_MARKER), 'utf8')).sha256;
|
|
276
|
+
} catch {
|
|
277
|
+
installed = null;
|
|
278
|
+
}
|
|
279
|
+
const hasSome = fs.existsSync(triggers) || (fs.existsSync(workflows) && fs.readdirSync(workflows).length > 0);
|
|
280
|
+
let action = 'replace';
|
|
281
|
+
if (!hasSome) action = 'install';
|
|
282
|
+
else if (installed === sha256) action = 'unchanged';
|
|
283
|
+
return { action, triggers, workflows, sha256 };
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
/** Carry out the plan. Returns where the previous ones were moved, when they were. */
|
|
287
|
+
function applyAutomations(plan, dataDir) {
|
|
288
|
+
if (plan.action !== 'install' && plan.action !== 'replace') return [];
|
|
289
|
+
const moved = [];
|
|
290
|
+
if (plan.action === 'replace') {
|
|
291
|
+
const stamp = new Date().toISOString().replace(/[:.]/g, '-');
|
|
292
|
+
for (const from of [plan.triggers, plan.workflows]) {
|
|
293
|
+
if (!fs.existsSync(from)) continue;
|
|
294
|
+
const to = `${from}.before-${stamp}`;
|
|
295
|
+
fs.renameSync(from, to);
|
|
296
|
+
moved.push(to);
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
fs.writeFileSync(
|
|
300
|
+
path.join(dataDir, AUTOMATIONS_MARKER),
|
|
301
|
+
JSON.stringify({ sha256: plan.sha256, installedAt: new Date().toISOString() }, null, 2) + '\n'
|
|
302
|
+
);
|
|
303
|
+
return moved;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
function describeAutomations(plan, moved) {
|
|
307
|
+
switch (plan.action) {
|
|
308
|
+
case 'none':
|
|
309
|
+
return "[start] workflows and triggers: this deploy carries none; the backend's own run\n";
|
|
310
|
+
case 'unchanged':
|
|
311
|
+
return '[start] workflows and triggers: unchanged (this deploy\'s are the ones already installed)\n';
|
|
312
|
+
case 'install':
|
|
313
|
+
return `[start] workflows and triggers: installing this deploy's backend/${AUTOMATIONS_FILE}\n`;
|
|
314
|
+
default:
|
|
315
|
+
return (
|
|
316
|
+
`[start] workflows and triggers: replaced with this deploy's backend/${AUTOMATIONS_FILE}` +
|
|
317
|
+
(moved.length ? `; the previous ones are kept as ${moved.join(' and ')}` : '') +
|
|
318
|
+
'\n'
|
|
319
|
+
);
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
|
|
243
323
|
/** Carry out the plan. Returns where the previous rules were moved, when they were. */
|
|
244
324
|
function applyRules(plan, dataDir) {
|
|
245
325
|
if (plan.action !== 'install' && plan.action !== 'replace') return null;
|
|
@@ -339,6 +419,10 @@ async function main() {
|
|
|
339
419
|
const plan = planRules(dataDir);
|
|
340
420
|
refuseDevOpen(plan);
|
|
341
421
|
process.stdout.write(describeRules(plan, `${plan.current}.before-<time>`).replace('[start]', '[check]'));
|
|
422
|
+
const automationsPlan = planAutomations(dataDir);
|
|
423
|
+
process.stdout.write(
|
|
424
|
+
describeAutomations(automationsPlan, [`${BACKEND_TRIGGERS}.before-<time>`, `${BACKEND_WORKFLOWS}.before-<time>`]).replace('[start]', '[check]')
|
|
425
|
+
);
|
|
342
426
|
process.exit(0);
|
|
343
427
|
}
|
|
344
428
|
port = await choosePort(argv, process.env);
|
|
@@ -349,8 +433,10 @@ async function main() {
|
|
|
349
433
|
fs.mkdirSync(dataDir, { recursive: true });
|
|
350
434
|
const rules = planRules(dataDir);
|
|
351
435
|
refuseDevOpen(rules);
|
|
436
|
+
const automations = planAutomations(dataDir);
|
|
352
437
|
const movedTo = applyRules(rules, dataDir);
|
|
353
438
|
process.stdout.write(describeRules(rules, movedTo));
|
|
439
|
+
process.stdout.write(describeAutomations(automations, applyAutomations(automations, dataDir)));
|
|
354
440
|
const functions = installFunctions(dataDir);
|
|
355
441
|
process.stdout.write(
|
|
356
442
|
functions.length
|
|
@@ -361,7 +447,9 @@ async function main() {
|
|
|
361
447
|
const args = ['serve', '--data-dir', dataDir, '--port', String(port), '--serve-app', APP, '--enforce-rules'];
|
|
362
448
|
// The backend installs the project's rules only when it has none, and says so loudly when it
|
|
363
449
|
// does not — so it is told about them only on a start that installs them.
|
|
364
|
-
|
|
450
|
+
// The same for the workflows and triggers, which it loads only into a backend that has none.
|
|
451
|
+
const installs = (plan) => plan.action === 'install' || plan.action === 'replace';
|
|
452
|
+
if (installs(rules) || installs(automations)) args.push('--project-dir', BACKEND);
|
|
365
453
|
const name = appName();
|
|
366
454
|
if (name) args.push('--backend-name', name);
|
|
367
455
|
args.push(...withoutOwnArgs(argv));
|
|
@@ -21,6 +21,8 @@
|
|
|
21
21
|
# /etc/<app>/secrets.env (written by ship-to-vm.sh over stdin, never here, never in the folder)
|
|
22
22
|
# /etc/systemd/system/<app>-backend.service
|
|
23
23
|
# /etc/caddy/conf.d/<app>.caddy (unless CADDY=off; deleted again if caddy validate fails)
|
|
24
|
+
# /opt/nodegx-native/sharp-<version>/ the image module that makes a photo's small versions, fetched from npm
|
|
25
|
+
# the first time any app ships here and shared by every app after
|
|
24
26
|
#
|
|
25
27
|
# and, once Caddy no longer points at it, removes /srv/<app>/ — the site the earlier layout (a site plus a loose
|
|
26
28
|
# cli.js) served from.
|
|
@@ -108,6 +110,44 @@ chown -R root:root "$FOLDER"; chmod -R u+rwX,go+rX,go-w "$FOLDER"
|
|
|
108
110
|
chown root:"$APP_USER" "$SECRETS"; chmod 640 "$SECRETS"
|
|
109
111
|
chown -R "$APP_USER":"$APP_USER" "/var/lib/$APP"; chmod 750 "/var/lib/$APP"
|
|
110
112
|
|
|
113
|
+
# 🔴 P112-OWN024-DEPLOYNOSHARP (Richard, 2026-10-25: the server fetches it once). A photo's small versions (?thumb=)
|
|
114
|
+
# are made by sharp, a native module the folder does not carry, so without it every thumbnail answers 501 and the
|
|
115
|
+
# marketplace draws its photos broken. It is fetched from npm ONCE per server into a place outside /opt/<app> (which
|
|
116
|
+
# every ship replaces), shared by every app here, and found through the unit's NODE_PATH. A server that cannot fetch
|
|
117
|
+
# it still ships: the app runs, and ?thumb= answers 501 until a later ship can. Pinned to the version the editor's
|
|
118
|
+
# backend ships (the lockfile's; tests/p112-own024-deploy-sharp.test.ts holds the two together).
|
|
119
|
+
SHARP_VERSION=0.33.5
|
|
120
|
+
NATIVE=/opt/nodegx-native
|
|
121
|
+
SHARP_DIR="$NATIVE/sharp-$SHARP_VERSION"
|
|
122
|
+
sharp_loads() { "$NODE" -e 'require(process.argv[1])' "$1/node_modules/sharp" >/dev/null 2>&1; }
|
|
123
|
+
if sharp_loads "$SHARP_DIR"; then
|
|
124
|
+
echo " photos: small versions (?thumb=) are made by sharp $SHARP_VERSION, already on this server"
|
|
125
|
+
else
|
|
126
|
+
mkdir -p "$NATIVE"
|
|
127
|
+
FETCH="$(mktemp -d "$NATIVE/.sharp-$SHARP_VERSION-XXXXXX")"
|
|
128
|
+
NPM="$(command -v npm || true)"
|
|
129
|
+
if [ -z "$NPM" ]; then
|
|
130
|
+
WHY="this server has no npm"
|
|
131
|
+
elif ! WHY="$(cd "$FETCH" && npm_config_cache="$FETCH/.cache" timeout 300 "$NPM" install --prefix "$FETCH" --omit=dev \
|
|
132
|
+
--no-audit --no-fund --loglevel=error --fetch-retries=1 "sharp@$SHARP_VERSION" 2>&1)"; then
|
|
133
|
+
# npm's last line points at a log in the cache this deletes; the line before it is the reason.
|
|
134
|
+
WHY="npm could not fetch it ($(echo "$WHY" | grep -v -e '^\s*$' -e 'complete log' | tail -1 | sed 's/^npm error //' || true))"
|
|
135
|
+
elif ! sharp_loads "$FETCH"; then
|
|
136
|
+
WHY="it was fetched but does not load on this server: $("$NODE" -e 'require(process.argv[1])' "$FETCH/node_modules/sharp" 2>&1 | grep -m1 -E '^[A-Za-z]*Error' || true)"
|
|
137
|
+
else
|
|
138
|
+
WHY=""
|
|
139
|
+
fi
|
|
140
|
+
rm -rf "$FETCH/.cache"
|
|
141
|
+
if [ -z "$WHY" ]; then
|
|
142
|
+
rm -rf "$SHARP_DIR"; mv "$FETCH" "$SHARP_DIR"; chmod -R go+rX,go-w "$SHARP_DIR"
|
|
143
|
+
echo " photos: fetched sharp $SHARP_VERSION from npm for this server's small photo versions (?thumb=), once"
|
|
144
|
+
else
|
|
145
|
+
rm -rf "$FETCH"
|
|
146
|
+
echo " photos: NOT AVAILABLE: small photo versions (?thumb=) answer 501 until sharp $SHARP_VERSION is on this server," \
|
|
147
|
+
"and the next ship fetches it again. $WHY. The app runs; uploads and full-size photos work."
|
|
148
|
+
fi
|
|
149
|
+
fi
|
|
150
|
+
|
|
111
151
|
cat > "$UNIT" <<UNIT
|
|
112
152
|
[Unit]
|
|
113
153
|
Description=${APP} (NodeGX full-stack folder, P26 DEP-002)
|
|
@@ -120,6 +160,7 @@ User=${APP_USER}
|
|
|
120
160
|
Group=${APP_USER}
|
|
121
161
|
WorkingDirectory=/var/lib/${APP}
|
|
122
162
|
EnvironmentFile=${SECRETS}
|
|
163
|
+
Environment=NODE_PATH=${SHARP_DIR}/node_modules
|
|
123
164
|
ExecStart=${NODE} ${FOLDER}/start.js --port ${PORT} --host 127.0.0.1 --data-dir ${DATA_DIR} --backend-id ${APP} --no-admin
|
|
124
165
|
Restart=always
|
|
125
166
|
RestartSec=3
|