Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
103 changes: 103 additions & 0 deletions packages/realm-server/scripts/codemod/searchable/republish.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
// Republish each (source -> published) pair so the published snapshot picks up
// the searchable annotations now in its source realm, then ASSERT the annotation
// landed: `realm publish` waits for the published realm to be ready (fully
// indexed), after which we read a known-changed module back from the published
// realm and confirm it carries `searchable`.
//
// node republish.mjs <pairs.json> <staging|prod> <out.jsonl> [--limit N]
// You do not need to source the seed before running: each boxel-cli call sources
// ~/.boxel-secrets/<env>.env itself, so the seed never enters this process.
import { execFileSync } from 'node:child_process';
import { readFileSync, writeFileSync, appendFileSync } from 'node:fs';
import { fileURLToPath } from 'node:url';
import { dirname, join } from 'node:path';

let [pairsPath, env, out] = process.argv.slice(2);
let limitIdx = process.argv.indexOf('--limit');
let limit = limitIdx > -1 ? Number(process.argv[limitIdx + 1]) : Infinity;
if (env !== 'staging' && env !== 'prod') {
// env is interpolated into the bash command that sources the seed file, so it
// must never carry arbitrary text.
throw new Error(
`env must be 'staging' or 'prod' (got ${JSON.stringify(env)})`,
);
}
const HERE = dirname(fileURLToPath(import.meta.url));
// …/scripts/codemod/searchable -> repo root
const REPO = join(HERE, '..', '..', '..', '..', '..');
let BOXEL = join(REPO, 'packages', 'boxel-cli', 'bin', 'boxel.js');

function boxel(args, timeout = 660000) {
let q = args.map((x) => `'${x.replace(/'/g, `'\\''`)}'`).join(' ');
let cmd = `set -a; . "$HOME/.boxel-secrets/${env}.env"; set +a; exec node ${JSON.stringify(BOXEL)} ${q}`;
Comment thread
habdelra marked this conversation as resolved.
return execFileSync('bash', ['-c', cmd], {
encoding: 'utf8',
timeout,
maxBuffer: 64 * 1024 * 1024,
});
}

let pairs = JSON.parse(readFileSync(pairsPath, 'utf8')).slice(0, limit);
writeFileSync(out, '');
let log = (o) => appendFileSync(out, JSON.stringify(o) + '\n');
let ok = 0,
fail = 0,
skipped = 0,
assertFail = 0;
process.stderr.write(`Republishing ${pairs.length} pair(s) on ${env}\n`);

for (let [i, p] of pairs.entries()) {
try {
// Waits for the published realm to be ready (fully indexed) by default.
boxel([
'realm',
'publish',
p.source,
p.published,
'--realm-secret-seed',
'--force',
'--timeout',
'600000',
]);
// Assert: the annotation is present in the published realm's source.
let r = JSON.parse(
boxel(['file', 'read', p.sampleModule, '--realm', p.published, '--json']),
);
let present = Boolean(r.ok && (r.content || '').includes('searchable'));
if (!present) assertFail++;
log({
published: p.published,
source: p.source,
sampleModule: p.sampleModule,
searchablePresent: present,
});
process.stderr.write(
` ${present ? '✓' : '⚠'} ${p.published} (searchable in ${p.sampleModule}: ${present})\n`,
);
ok++;
} catch (e) {
let msg = String(e.message);
// A realm the server refuses to publish ("not publishable") is an expected
// skip, not a rollout failure — count it separately so it never trips the
// nonzero exit below.
let notPublishable = /not publishable/.test(msg);
if (notPublishable) skipped++;
else fail++;
log({
published: p.published,
source: p.source,
...(notPublishable ? { skipped: true } : {}),
error: msg.split('\n').slice(0, 2).join(' | '),
});
process.stderr.write(
` ${notPublishable ? '⊘ skip (not publishable):' : '✗'} ${p.published} — ${msg.split('\n')[0]}\n`,
);
}
process.stderr.write(` …${i + 1}/${pairs.length}\n`);
}
process.stderr.write(
`\nDone. ${ok} republished, ${assertFail} missing-searchable, ${skipped} skipped (not publishable), ${fail} failed.\n`,
);
Comment thread
habdelra marked this conversation as resolved.
// Surface real failures to callers/CI; expected "not publishable" skips do not
// fail the run.
process.exit(fail > 0 || assertFail > 0 ? 1 : 0);
Loading