@bongos/core 1.19.90 → 1.19.92
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/.bongos-core.json +34 -29
- package/config/branding.neutral.json +6 -6
- package/modules/dev-box/app/assets/tray/win-alert.png +0 -0
- package/modules/dev-box/app/assets/tray/win-on.png +0 -0
- package/modules/dev-box/app/build-resources/icon.png +0 -0
- package/modules/dev-box/app/renderer/theme.generated.css +9 -9
- package/modules/dev-box/app/scripts/gen-brand.js +10 -10
- package/modules/dev-box/app/scripts/gen-dmg-backdrop.py +3 -3
- package/modules/dev-box/app/scripts/gen-icons.js +3 -3
- package/modules/dev-box/app/src/brand.generated.json +1 -1
- package/package-lock.json +2 -2
- package/package.json +1 -1
- package/public-landing/assets/cosmos.css +112 -64
- package/public-landing/index.html +543 -88
- package/scripts/gds/upgrade.js +47 -4
- package/scripts/migrate.sh +60 -3
- package/scripts/status-mirror-sync.js +12 -6
- package/src/bongos/serve-internal.js +53 -10
- package/src/module-api.js +5 -1
- package/src/platform-server.js +26 -2
- package/tests/cloud_bongos_pack.mjs +11 -7
- package/tests/landing_page.mjs +289 -0
- package/tests/migrate_root_resolution.mjs +100 -2
- package/tests/upgrade.mjs +58 -0
package/scripts/gds/upgrade.js
CHANGED
|
@@ -324,6 +324,42 @@ function runMigrate(instanceDir, run = spawnSync) {
|
|
|
324
324
|
return !r || r.error ? { ok: false, reason: r && r.error ? r.error.message : 'spawn failed' } : { ok: r.status === 0, code: r.status };
|
|
325
325
|
}
|
|
326
326
|
|
|
327
|
+
// WHICH database will migrate target? (task 1002755)
|
|
328
|
+
//
|
|
329
|
+
// The pre-flight probe below is only meaningful if it tests the SAME database the migration
|
|
330
|
+
// will later use. It used to be handed `process.env.PGDATABASE` — undefined on a normal box —
|
|
331
|
+
// so psql fell through to a database named after the OS user, failed for an unrelated reason,
|
|
332
|
+
// and soft-skipped with "psql failed for another reason". migrate then picked its own name and
|
|
333
|
+
// hit a database that did not exist. Two soft failures composing into a hard one, discovered
|
|
334
|
+
// only AFTER the pin had moved.
|
|
335
|
+
//
|
|
336
|
+
// So resolve it here, up front, by asking the core's OWN resolver in a child pointed at this
|
|
337
|
+
// instance — the same precedence migrate.sh uses (PGDATABASE wins, else the instance's branding
|
|
338
|
+
// pack) and the same function, so the probe and the migration cannot disagree. Spawned rather
|
|
339
|
+
// than required because requiring pool.js constructs a pg Pool in this process.
|
|
340
|
+
// Returns { db, ran }:
|
|
341
|
+
// { db: 'name', ran: true } — resolved
|
|
342
|
+
// { db: null, ran: true } — the resolver RAN and this instance names no database. Fatal:
|
|
343
|
+
// migrate cannot proceed and we know it before the pin moves.
|
|
344
|
+
// { db: null, ran: false } — the resolver could not run at all (no node_modules yet, a
|
|
345
|
+
// stubbed spawn in tests). NOT fatal: that is a different problem
|
|
346
|
+
// from "no database configured", and guessing which one it is would
|
|
347
|
+
// block bumps for the wrong reason. migrate.sh makes the same
|
|
348
|
+
// distinction and still fails loudly later, so the net holds.
|
|
349
|
+
function resolveInstanceDb({ instanceDir }, run = spawnSync) {
|
|
350
|
+
if (process.env.PGDATABASE) return { db: process.env.PGDATABASE, ran: true };
|
|
351
|
+
const poolPath = path.join(instanceDir, 'node_modules', CORE_PKG, 'src', 'bongos', 'pool');
|
|
352
|
+
// the module path rides as argv, never interpolated into JS source — a Windows-shaped path's
|
|
353
|
+
// backslashes are escape sequences inside a string literal
|
|
354
|
+
const r = run(process.execPath, ['-e', "process.stdout.write(require(process.argv[1]).instanceDbName() || '')", poolPath], {
|
|
355
|
+
cwd: instanceDir,
|
|
356
|
+
encoding: 'utf8',
|
|
357
|
+
env: { ...process.env, CLOUDBONGOS_INSTANCE_ROOT: instanceDir },
|
|
358
|
+
});
|
|
359
|
+
if (!r || r.error || r.status !== 0 || typeof r.stdout !== 'string') return { db: null, ran: false };
|
|
360
|
+
return { db: String(r.stdout).trim() || null, ran: true };
|
|
361
|
+
}
|
|
362
|
+
|
|
327
363
|
// Regenerate the per-instance API artifacts (OpenAPI spec + api-reference + typed
|
|
328
364
|
// client) from the NEW core's routes — the claude-materialize sibling (task 2051).
|
|
329
365
|
// WHY: the routes + generators ship WITH core, and the generated spec/docs/client also
|
|
@@ -748,10 +784,17 @@ async function runUpgrade(opts, deps = {}) {
|
|
|
748
784
|
// mistake, and finding it at migrate means unwinding a pin that never should have
|
|
749
785
|
// been written (task 1002712).
|
|
750
786
|
if (!opts.skipMigrate) {
|
|
751
|
-
const
|
|
787
|
+
const { db, ran } = (deps.resolveInstanceDb || resolveInstanceDb)({ instanceDir }, run);
|
|
788
|
+
if (ran && !db) {
|
|
789
|
+
return { ok: false, error: 'cannot determine which database to migrate.\n'
|
|
790
|
+
+ ' Set PGDATABASE, or give this instance a db.database in config/branding.json.\n'
|
|
791
|
+
+ ' Checked BEFORE the pin moves: migrate runs after the install, so a name that\n'
|
|
792
|
+
+ ' cannot be resolved there costs a full rollback to discover (task 1002755).' };
|
|
793
|
+
}
|
|
794
|
+
const dbId = (deps.preflightDbIdentity || preflightDbIdentity)({ instanceDir, db }, run);
|
|
752
795
|
if (!dbId.ok) return { ok: false, error: dbId.reason };
|
|
753
|
-
if (dbId.skipped) log(` • db identity pre-check: ${dbId.skipped}`);
|
|
754
|
-
else log(
|
|
796
|
+
if (dbId.skipped) log(` • db identity pre-check${db ? ` (${db})` : ''}: ${dbId.skipped}`);
|
|
797
|
+
else log(` ✓ pre-flight: database${db ? ` "${db}"` : ''} reachable as the current user`);
|
|
755
798
|
}
|
|
756
799
|
|
|
757
800
|
// 1b. mandatory post-restart health check (audit H9 / task 1002222, finding F5). A bump that
|
|
@@ -1005,7 +1048,7 @@ async function main(argv = process.argv.slice(2)) {
|
|
|
1005
1048
|
module.exports = {
|
|
1006
1049
|
runUpgrade, parseArgs, main, rollback,
|
|
1007
1050
|
versionFromTgzPath, versionFromDep, readInstalledCoreVersion, readPinnedCoreVersion, writePin, writeRegistryPin, restorePin,
|
|
1008
|
-
vendorTarball, gitTreeClean, persistPin, dirtyPinFiles, gitCurrentBranch, PIN_FILES, preflightDbIdentity, preflightModules, reportModulePreflight, npmInstall, runMigrate, regenerateApiArtifacts, regenerateNavDocs, NAV_WHOLE_FILE_GENERATORS, missingDocAssets, healDocAssets, DOC_ASSET_GENERATORS, restartService, recordLedger, pollHealth,
|
|
1051
|
+
vendorTarball, gitTreeClean, persistPin, dirtyPinFiles, gitCurrentBranch, PIN_FILES, preflightDbIdentity, resolveInstanceDb, preflightModules, reportModulePreflight, npmInstall, runMigrate, regenerateApiArtifacts, regenerateNavDocs, NAV_WHOLE_FILE_GENERATORS, missingDocAssets, healDocAssets, DOC_ASSET_GENERATORS, restartService, recordLedger, pollHealth,
|
|
1009
1052
|
readManifest, resolveReferenceManifest, verifyInstalledPin, // task 1002216 (audit H8) — integrity-pin verification
|
|
1010
1053
|
CORE_PKG, ARTIFACT,
|
|
1011
1054
|
};
|
package/scripts/migrate.sh
CHANGED
|
@@ -32,7 +32,13 @@ INVOKE_CWD="${INIT_CWD:-$PWD}"
|
|
|
32
32
|
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
33
33
|
DEFAULT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
|
|
34
34
|
CORE_ROOT="$(cd "$DEFAULT_ROOT" && node -e "process.stdout.write(require('./src/instance-config').resolveCoreRoot())" 2>/dev/null || echo "$DEFAULT_ROOT")"
|
|
35
|
-
|
|
35
|
+
# The module path rides in as argv rather than being interpolated into the JS source: a
|
|
36
|
+
# Windows-shaped DEFAULT_ROOT contains backslashes, which are ESCAPE SEQUENCES inside a JS
|
|
37
|
+
# string literal ("\U", "\r", "\D"), so interpolating corrupts the path, the require throws,
|
|
38
|
+
# and this silently falls back to DEFAULT_ROOT — precisely the F6 shape described above, just
|
|
39
|
+
# triggered by the path separator instead of the cwd. Fixed alongside task 1002755, which hit
|
|
40
|
+
# the identical bug one resolution further down.
|
|
41
|
+
INSTANCE_ROOT="$(cd "$INVOKE_CWD" && node -e "process.stdout.write(require(process.argv[1]).resolveInstanceRootExplicit() || '')" "$DEFAULT_ROOT/src/instance-config" 2>/dev/null || true)"
|
|
36
42
|
INSTANCE_ROOT="${INSTANCE_ROOT:-$DEFAULT_ROOT}"
|
|
37
43
|
# Run from the instance root so instance-relative paths resolve (the post-deploy art check below)
|
|
38
44
|
# AND so the enabledModules() read finds the instance's config/modules.json (resolveInstanceRoot
|
|
@@ -49,17 +55,68 @@ if [ "${1:-}" = "--print-roots" ]; then
|
|
|
49
55
|
exit 0
|
|
50
56
|
fi
|
|
51
57
|
|
|
52
|
-
DB="${PGDATABASE:-amazonprimea}"
|
|
53
|
-
|
|
54
58
|
# Test/ops seam (added with the 1.18.19 space-in-path fix): --print-migrations walks the REAL core-migration loop but reports
|
|
55
59
|
# each path instead of applying it, and touches NO DB. tests/migrate_root_resolution.mjs uses it to
|
|
56
60
|
# assert that a checkout whose path contains a space still yields whole, readable paths. Reporting
|
|
57
61
|
# through the real loop (rather than a copy of it in the test) is the point: if the loop ever reverts
|
|
58
62
|
# to an unquoted `for f in $MIGRATIONS`, this seam fragments the paths and the test fails. Also handy
|
|
59
63
|
# in the field — `npm run migrate -- --print-migrations` shows exactly what migrate would apply.
|
|
64
|
+
#
|
|
65
|
+
# Resolved BEFORE the database name below, because a dry run touches no DB (every psql call is
|
|
66
|
+
# guarded by DRY_RUN) and so must not require one.
|
|
60
67
|
DRY_RUN=0
|
|
61
68
|
if [ "${1:-}" = "--print-migrations" ]; then DRY_RUN=1; fi
|
|
62
69
|
|
|
70
|
+
# --- Which database? (task 1002755) ---------------------------------------
|
|
71
|
+
# SAME precedence as src/bongos/pool.js: an explicit PGDATABASE wins, else the instance's own
|
|
72
|
+
# branding pack names it (config/branding.json db.database, else the neutral starter). Resolved by
|
|
73
|
+
# CALLING pool.instanceDbName() rather than re-reading the config here, so the migrator and the
|
|
74
|
+
# running server can never disagree about which database this instance is.
|
|
75
|
+
#
|
|
76
|
+
# There is deliberately NO literal fallback. This script ships inside the core to EVERY instance,
|
|
77
|
+
# so a baked-in default is one host's database name pointed at everyone else's box: on a co-hosted
|
|
78
|
+
# machine it silently targets a stranger's data, and everywhere else it dies with a confusing
|
|
79
|
+
# "database ... does not exist" long after the caller has moved its version pin. pool.js already
|
|
80
|
+
# made exactly this call for the server ("NOT hard-defaulting to a host's DB name", pool.js:10-16);
|
|
81
|
+
# this is the migrator catching up, not a new policy.
|
|
82
|
+
# The module path rides in as argv, NOT interpolated into the JS source: a Windows-shaped
|
|
83
|
+
# CORE_ROOT contains backslashes, and inside a JS string literal those are escape sequences
|
|
84
|
+
# ("\U", "\r"), so interpolating silently corrupts the path and this resolution degrades to
|
|
85
|
+
# empty. argv values are never parsed as JS.
|
|
86
|
+
DB="${PGDATABASE:-}"
|
|
87
|
+
DB_RESOLVER_ERR=""
|
|
88
|
+
if [ -z "$DB" ]; then
|
|
89
|
+
# stderr is CAPTURED, not discarded: "the pack names no database" and "the resolver could not
|
|
90
|
+
# run at all" are different problems with different fixes, and collapsing both into an empty
|
|
91
|
+
# string sends the operator to edit a config file when the real answer is `npm install`.
|
|
92
|
+
# (pool.js requires pg, so an instance with no node_modules fails here rather than resolving.)
|
|
93
|
+
if DB_RESOLVED="$(node -e "process.stdout.write(require(process.argv[1]).instanceDbName() || '')" "$CORE_ROOT/src/bongos/pool" 2>&1)"; then
|
|
94
|
+
DB="$DB_RESOLVED"
|
|
95
|
+
else
|
|
96
|
+
DB_RESOLVER_ERR="$DB_RESOLVED"
|
|
97
|
+
fi
|
|
98
|
+
fi
|
|
99
|
+
if [ -z "$DB" ] && [ "$DRY_RUN" = 0 ]; then
|
|
100
|
+
echo "migrate: cannot determine which database to migrate." >&2
|
|
101
|
+
if [ -n "$DB_RESOLVER_ERR" ]; then
|
|
102
|
+
echo " the db-name resolver did not run: $(printf '%s' "$DB_RESOLVER_ERR" | head -n 1)" >&2
|
|
103
|
+
echo " if the core's dependencies are not installed yet, run: npm install" >&2
|
|
104
|
+
else
|
|
105
|
+
echo " Set PGDATABASE, or give this instance a db.database in config/branding.json." >&2
|
|
106
|
+
fi
|
|
107
|
+
echo " Refusing to guess: a baked-in default would target another instance's database." >&2
|
|
108
|
+
exit 2
|
|
109
|
+
fi
|
|
110
|
+
|
|
111
|
+
# Test/ops seam: --print-db reports the database this run WOULD migrate, and exits before any DB
|
|
112
|
+
# work. `bongos upgrade` calls it to learn the name up front, so its pre-flight can prove the DB is
|
|
113
|
+
# reachable BEFORE the version pin moves — otherwise an unresolvable name is only discovered at
|
|
114
|
+
# migrate time, and the pin has to be unwound (task 1002755).
|
|
115
|
+
if [ "${1:-}" = "--print-db" ]; then
|
|
116
|
+
printf '%s\n' "$DB"
|
|
117
|
+
exit 0
|
|
118
|
+
fi
|
|
119
|
+
|
|
63
120
|
# --- Instance-seed policy (task 1704 / ADR 0105) --------------------------
|
|
64
121
|
# OTB/product scope-truth (versions, goals, done_when_criteria) lives in seed
|
|
65
122
|
# migrations under migrations/instance/ (plus a few guarded blocks embedded in the
|
|
@@ -57,17 +57,23 @@ const { branding, clientBranding } = require('../src/branding');
|
|
|
57
57
|
// with src/bongos/serve-internal.js applyBrandTokens. `brand` is the client-safe pack
|
|
58
58
|
// and `publicOrigin` the resolved origin — both injected, never read ambiently, so the
|
|
59
59
|
// transform stays pure and testable.
|
|
60
|
-
function brandTokens(html, brand = {}, publicOrigin = '') {
|
|
60
|
+
function brandTokens(html, brand = {}, publicOrigin = '', buildersOrigin = '') {
|
|
61
61
|
const world = (brand.identity && brand.identity.worldName) || '';
|
|
62
62
|
const product = (brand.identity && brand.identity.productName) || '';
|
|
63
|
+
const tagline = (brand.copy && brand.copy.tagline) || '';
|
|
63
64
|
const cur = (brand.currency && brand.currency.label) || 'credits';
|
|
64
65
|
const Cur = cur ? cur.charAt(0).toUpperCase() + cur.slice(1) : cur;
|
|
66
|
+
const esc = (s) => String(s == null ? '' : s)
|
|
67
|
+
.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>')
|
|
68
|
+
.replace(/"/g, '"').replace(/'/g, ''');
|
|
65
69
|
return html
|
|
66
|
-
.split('{{worldName}}').join(world)
|
|
67
|
-
.split('{{productName}}').join(product)
|
|
68
|
-
.split('{{Currency}}').join(Cur)
|
|
69
|
-
.split('{{currency}}').join(cur)
|
|
70
|
-
.split('{{
|
|
70
|
+
.split('{{worldName}}').join(esc(world))
|
|
71
|
+
.split('{{productName}}').join(esc(product))
|
|
72
|
+
.split('{{Currency}}').join(esc(Cur))
|
|
73
|
+
.split('{{currency}}').join(esc(cur))
|
|
74
|
+
.split('{{tagline}}').join(esc(tagline))
|
|
75
|
+
.split('{{publicOrigin}}').join(esc(publicOrigin || ''))
|
|
76
|
+
.split('{{buildersOrigin}}').join(esc(buildersOrigin || publicOrigin || ''));
|
|
71
77
|
}
|
|
72
78
|
|
|
73
79
|
// ---- the three off-box index.html injections -------------------------------
|
|
@@ -153,21 +153,45 @@ function injectBrandingGlobal(html, brand = clientBranding(), mods = clientModul
|
|
|
153
153
|
// stays brand-free (the fitness identity gate). Runs BEFORE the __BRANDING__
|
|
154
154
|
// script is inserted; the injected JSON holds resolved values (never tokens), so
|
|
155
155
|
// order is immaterial. Tokens: {{worldName}} {{productName}} {{currency}}
|
|
156
|
-
// (lowercase label) {{Currency}} (Capitalized) {{
|
|
157
|
-
//
|
|
158
|
-
//
|
|
159
|
-
// `
|
|
160
|
-
|
|
156
|
+
// (lowercase label) {{Currency}} (Capitalized) {{tagline}} (the pack's one-line
|
|
157
|
+
// pitch), plus the two ORIGINS — {{publicOrigin}} (this instance's apex) and
|
|
158
|
+
// {{buildersOrigin}} (its hall). Both origins are sourced from the FULL pack's
|
|
159
|
+
// `domains`, which clientBranding deliberately omits, so they are the only way a
|
|
160
|
+
// served page can carry a correct cross-host href or canonical/og: URL: a client
|
|
161
|
+
// fill cannot reach them, and a baked one would make every instance advertise
|
|
162
|
+
// whichever instance happened to author the file (task 1002748, direction C5/C6).
|
|
163
|
+
// Pure + exported for the test; both origins are injectable so it drives with zero I/O.
|
|
164
|
+
function applyBrandTokens(html, brand = clientBranding(), origin = safePublicOrigin(), hallOrigin = safeBuildersOrigin(), sourceRepo = safeSourceRepoUrl()) {
|
|
161
165
|
const world = (brand.identity && brand.identity.worldName) || '';
|
|
162
166
|
const product = (brand.identity && brand.identity.productName) || '';
|
|
167
|
+
const tagline = (brand.copy && brand.copy.tagline) || '';
|
|
163
168
|
const cur = (brand.currency && brand.currency.label) || 'credits';
|
|
164
169
|
const Cur = cur ? cur.charAt(0).toUpperCase() + cur.slice(1) : cur;
|
|
165
170
|
return String(html == null ? '' : html)
|
|
166
|
-
.split('{{worldName}}').join(world)
|
|
167
|
-
.split('{{productName}}').join(product)
|
|
168
|
-
.split('{{Currency}}').join(Cur)
|
|
169
|
-
.split('{{currency}}').join(cur)
|
|
170
|
-
.split('{{
|
|
171
|
+
.split('{{worldName}}').join(esc(world))
|
|
172
|
+
.split('{{productName}}').join(esc(product))
|
|
173
|
+
.split('{{Currency}}').join(esc(Cur))
|
|
174
|
+
.split('{{currency}}').join(esc(cur))
|
|
175
|
+
.split('{{tagline}}').join(esc(tagline))
|
|
176
|
+
.split('{{publicOrigin}}').join(esc(origin || ''))
|
|
177
|
+
// a pack that names no hall of its own resolves to its OWN apex — never to
|
|
178
|
+
// whichever instance authored the file
|
|
179
|
+
.split('{{buildersOrigin}}').join(esc(hallOrigin || origin || ''))
|
|
180
|
+
.split('{{sourceRepoUrl}}').join(esc(sourceRepo || ''));
|
|
181
|
+
}
|
|
182
|
+
// Every token lands in HTML — as element text, or inside a double-quoted
|
|
183
|
+
// attribute (href/content). Escaping is defence in depth, not a live fix: the
|
|
184
|
+
// only source is config/branding.json, which is committed, code-reviewed and
|
|
185
|
+
// Archon-only, never request-supplied. But a pack string carrying a quote or a
|
|
186
|
+
// '<' would otherwise break out of its attribute or close <title> early, and
|
|
187
|
+
// the values now reach four times as many places as when this was written.
|
|
188
|
+
// No token is ever emitted into a <script> or a CSS context (checked across
|
|
189
|
+
// public-landing/, hall-ui/, status-ui/) — that would need different escaping.
|
|
190
|
+
// Ampersand first, or the other replacements get double-escaped.
|
|
191
|
+
function esc(s) {
|
|
192
|
+
return String(s == null ? '' : s)
|
|
193
|
+
.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>')
|
|
194
|
+
.replace(/"/g, '"').replace(/'/g, ''');
|
|
171
195
|
}
|
|
172
196
|
// The instance apex origin from the full branding pack (domains is server-only,
|
|
173
197
|
// so it never rides clientBranding). Best-effort so a branding hiccup can't crash
|
|
@@ -175,6 +199,25 @@ function applyBrandTokens(html, brand = clientBranding(), origin = safePublicOri
|
|
|
175
199
|
function safePublicOrigin() {
|
|
176
200
|
try { return branding().domains.publicOrigin || ''; } catch { return ''; }
|
|
177
201
|
}
|
|
202
|
+
// The instance's OWN builders' hall origin, same server-only source. Empty is fine:
|
|
203
|
+
// applyBrandTokens falls back to the apex, so a pack that names no hall still gets
|
|
204
|
+
// a same-instance link rather than a dangling href.
|
|
205
|
+
function safeBuildersOrigin() {
|
|
206
|
+
try { return branding().domains.buildersOrigin || ''; } catch { return ''; }
|
|
207
|
+
}
|
|
208
|
+
// The instance's own source repo, from the pack's `repo: {owner, name}` binding —
|
|
209
|
+
// another server-only field (the branding contract keeps repo off clientBranding
|
|
210
|
+
// alongside domains). A page that wants to link "read the source" must take it
|
|
211
|
+
// from here: the authorship audit fails any hardcoded owner/name, and rightly so,
|
|
212
|
+
// since a baked one would point every instance at whoever authored the file.
|
|
213
|
+
// '' when the pack names no repo — the page drops the link rather than emit a
|
|
214
|
+
// dangling href.
|
|
215
|
+
function safeSourceRepoUrl() {
|
|
216
|
+
try {
|
|
217
|
+
const repo = branding().repo;
|
|
218
|
+
return repo && repo.owner && repo.name ? `https://github.com/${repo.owner}/${repo.name}` : '';
|
|
219
|
+
} catch { return ''; }
|
|
220
|
+
}
|
|
178
221
|
// Instance product/world name for the API-discovery index (task 1918). Best-effort:
|
|
179
222
|
// a branding hiccup must not break the JSON response.
|
|
180
223
|
function safeProductName() {
|
package/src/module-api.js
CHANGED
|
@@ -607,8 +607,12 @@ const { validateOrRespond, LIMITS, parseId, asyncHandler, corsPublicGet, parsePa
|
|
|
607
607
|
// landed since 1.19.88 with no explicit bump. run 31657467485. (task 1002620)
|
|
608
608
|
// 1.19.90 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
|
|
609
609
|
// landed since 1.19.89 with no explicit bump. run 31717551308. (task 1002620)
|
|
610
|
+
// 1.19.91 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
|
|
611
|
+
// landed since 1.19.90 with no explicit bump. run 31722841411. (task 1002620)
|
|
612
|
+
// 1.19.92 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
|
|
613
|
+
// landed since 1.19.91 with no explicit bump. run 31727411036. (task 1002620)
|
|
610
614
|
// ---------------------------------------------------------------------------
|
|
611
|
-
const CORE_VERSION = '1.19.
|
|
615
|
+
const CORE_VERSION = '1.19.92'; // CI auto-patch carrier (ADR 0161); see changelog above
|
|
612
616
|
|
|
613
617
|
// A namespaced logger so a module's log lines are attributable + consistent.
|
|
614
618
|
// Usage: const log = api.logger('dev-box'); log.info('mounted');
|
package/src/platform-server.js
CHANGED
|
@@ -65,12 +65,36 @@ function landingPage(file) {
|
|
|
65
65
|
if (!_landingPages.has(file)) {
|
|
66
66
|
let html = '';
|
|
67
67
|
try {
|
|
68
|
-
html = injectBrandingGlobal(fs.readFileSync(path.join(LANDING_DIR, file), 'utf8'));
|
|
68
|
+
html = injectBrandingGlobal(stripApexOnly(fs.readFileSync(path.join(LANDING_DIR, file), 'utf8')));
|
|
69
69
|
} catch { /* absent → '' */ }
|
|
70
70
|
_landingPages.set(file, html);
|
|
71
71
|
}
|
|
72
72
|
return _landingPages.get(file);
|
|
73
73
|
}
|
|
74
|
+
|
|
75
|
+
// THE APEX CONDITIONAL (task 1002748). public-landing/ ships wholesale to every
|
|
76
|
+
// instance, so one file has to render two shapes: the platform's own front door,
|
|
77
|
+
// and a child project's. Sections that are true only of the platform — the
|
|
78
|
+
// catalog of OTHER instances, the "capture-resistant commons" claim, the door
|
|
79
|
+
// into the catalog — are wrapped in <!--apex-only:start--> … <!--apex-only:end-->
|
|
80
|
+
// and REMOVED here on any instance that is not the hub.
|
|
81
|
+
//
|
|
82
|
+
// Removed, not CSS-hidden: a child instance must not ship the platform's claims
|
|
83
|
+
// to a crawler, and hiding is not the same as not saying it.
|
|
84
|
+
//
|
|
85
|
+
// Comment sentinels rather than an attribute selector because this is a string
|
|
86
|
+
// transform, not a DOM one — matching a balanced close tag with a regex is the
|
|
87
|
+
// fragile shape, and a sentinel pair has no such failure mode.
|
|
88
|
+
//
|
|
89
|
+
// `platform-identity` IS the hub flag, not a proxy for it: its manifest reads
|
|
90
|
+
// "Enabled ONLY on the hub instance; ordinary hosted + self-hosted instances
|
|
91
|
+
// leave it off", and it is the module that owns the very /projects catalog these
|
|
92
|
+
// sections point at. So this asks the instance what it IS, and nothing anywhere
|
|
93
|
+
// has to know the platform's hostname (direction C5).
|
|
94
|
+
const APEX_ONLY = /<!--apex-only:start-->[\s\S]*?<!--apex-only:end-->/g;
|
|
95
|
+
function stripApexOnly(html, isHub = isModuleEnabled('platform-identity')) {
|
|
96
|
+
return isHub ? html : String(html).replace(APEX_ONLY, '');
|
|
97
|
+
}
|
|
74
98
|
function landingHtml() { return landingPage('index.html'); }
|
|
75
99
|
// The interactive landing gate (public-landing/gate/, ported from amazonprimea
|
|
76
100
|
// in task 1002407). When an instance ships it, it is the apex front door
|
|
@@ -232,4 +256,4 @@ if (require.main === module) {
|
|
|
232
256
|
});
|
|
233
257
|
}
|
|
234
258
|
|
|
235
|
-
module.exports = { buildPlatformApp, startBackgroundServices };
|
|
259
|
+
module.exports = { buildPlatformApp, startBackgroundServices, stripApexOnly };
|
|
@@ -34,14 +34,16 @@ test('R74: the vanilla pack carries the Cloud Bongos LOOK (sunrise palette, Balo
|
|
|
34
34
|
const c = clientBranding(vanilla());
|
|
35
35
|
// fired down from the original #f97316 so white-on-accent clears WCAG AA
|
|
36
36
|
// (2.80:1 -> 4.61:1, task 1002749) — same ember hue, readable
|
|
37
|
-
assert.equal(c.theme.colors.accent, '#ce4806', '
|
|
38
|
-
|
|
37
|
+
assert.equal(c.theme.colors.accent, '#ce4806', 'ember orange accent');
|
|
38
|
+
// ember marigold (task 1002748): the base is PIGMENTED, not near-white. The
|
|
39
|
+
// old #fff7ed sat within a few points of the cards and the page read as fog.
|
|
40
|
+
assert.equal(c.theme.colors.bg, '#f9d9a8', 'marigold base');
|
|
39
41
|
assert.match(c.theme.fonts.display, /Baloo 2/);
|
|
40
42
|
assert.match(c.theme.fonts.body, /Quicksand/);
|
|
41
43
|
assert.ok(c.theme.fonts.links.some((l) => /Baloo\+2/.test(l) && /Quicksand/.test(l) && /Fira\+Code/.test(l)), 'loads Baloo 2 + Quicksand + Fira Code');
|
|
42
44
|
assert.match(c.theme.favicon, /circle/, 'bongo-pair favicon (circles), not the Greek Α');
|
|
43
45
|
assert.doesNotMatch(c.theme.favicon, /font-family='serif'>Α/);
|
|
44
|
-
assert.
|
|
46
|
+
assert.equal(c.theme.backdrop, '#f9d9a8', 'the marigold ground holds all the way down');
|
|
45
47
|
assert.equal(c.copy.tagline, 'Building software is laying down a beat.');
|
|
46
48
|
assert.equal(c.rankLabels.archon, 'Owner', 'plain rank labels, not "Archon"');
|
|
47
49
|
});
|
|
@@ -50,9 +52,9 @@ test('R74: serving the hall on a vanilla boot injects the Cloud Bongos sunrise t
|
|
|
50
52
|
const c = clientBranding(vanilla());
|
|
51
53
|
const html = '<head><link rel="stylesheet" href="/builders/style.css"><title>x</title></head><body></body>';
|
|
52
54
|
const out = injectBrandingGlobal(html, c);
|
|
53
|
-
// palette override +
|
|
55
|
+
// palette override + the marigold body backdrop + bongo favicon + the type links
|
|
54
56
|
assert.match(out, /:root\{[^}]*--accent:#ce4806;/);
|
|
55
|
-
assert.match(out, /body\{background
|
|
57
|
+
assert.match(out, /body\{background:#f9d9a8;\}/);
|
|
56
58
|
assert.match(out, /<link rel="icon" href="data:image\/svg\+xml[^"]*circle/);
|
|
57
59
|
assert.match(out, /fonts\.googleapis\.com\/css2\?family=Baloo\+2/);
|
|
58
60
|
// and it lands AFTER the stylesheet so the Cloud Bongos look wins
|
|
@@ -63,8 +65,10 @@ test('R74: themeMarkup paints the backdrop only when the pack supplies one', ()
|
|
|
63
65
|
// OTB-shaped pack (no backdrop) → no body{background} rule.
|
|
64
66
|
const otb = themeMarkup({ colors: { bg: '#ece0c2' }, fonts: { links: [] }, favicon: '', backdrop: '' });
|
|
65
67
|
assert.doesNotMatch(otb, /body\{background/);
|
|
66
|
-
// Cloud Bongos pack → backdrop present.
|
|
67
|
-
|
|
68
|
+
// Cloud Bongos pack → backdrop present. Asserted by PRESENCE, not by shape:
|
|
69
|
+
// this test's subject is whether a backdrop paints at all, so it must not
|
|
70
|
+
// re-break every time the palette legitimately changes.
|
|
71
|
+
assert.match(themeMarkup(clientBranding(vanilla()).theme), /body\{background:.+\}/);
|
|
68
72
|
});
|
|
69
73
|
|
|
70
74
|
test('customer-0: GDS_BRANDING_FILE selects the cloudbongos.com instance pack (task 1374)', () => {
|