feat: publish Curios directly through the Git source
This commit is contained in:
+10
-4
@@ -377,6 +377,7 @@ async fn main() -> Result<()> {
|
||||
return remote_source_check(
|
||||
args.get(2).context("remote source URL required")?,
|
||||
args.get(3).context("remote source ID required")?,
|
||||
args.get(4).map(String::as_str).unwrap_or("http"),
|
||||
)
|
||||
.await;
|
||||
}
|
||||
@@ -477,7 +478,12 @@ async fn main() -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn remote_source_check(url: &str, source_id: &str) -> Result<()> {
|
||||
async fn remote_source_check(url: &str, source_id: &str, kind: &str) -> Result<()> {
|
||||
let kind = match kind {
|
||||
"git" => SourceKind::Git,
|
||||
"http" => SourceKind::Http,
|
||||
_ => anyhow::bail!("Remote source kind must be git or http"),
|
||||
};
|
||||
let sandbox = tempfile::Builder::new()
|
||||
.prefix("trove-remote-qa-")
|
||||
.tempdir_in("/tmp")?;
|
||||
@@ -497,7 +503,7 @@ async fn remote_source_check(url: &str, source_id: &str) -> Result<()> {
|
||||
packages.add_source(SourceConfig {
|
||||
id: source_id.into(),
|
||||
name: source_id.into(),
|
||||
kind: SourceKind::Http,
|
||||
kind,
|
||||
index: url.into(),
|
||||
enabled: true,
|
||||
priority: 0,
|
||||
@@ -599,8 +605,8 @@ async fn remote_source_check(url: &str, source_id: &str) -> Result<()> {
|
||||
sandbox.close()?;
|
||||
let failed = rows.iter().filter(|row| row["status"] == "fail").count();
|
||||
emit(json!({
|
||||
"schemaVersion":1,"sourceUrl":url,"sourceId":source_id,"target":target(),
|
||||
"scope":"Remote HTTP index and packages via real PackageManager; installed native backends via real UDS. Desktop UI rendering is not included.",
|
||||
"schemaVersion":1,"sourceUrl":url,"sourceId":source_id,"sourceKind":kind,"target":target(),
|
||||
"scope":"Remote source and packages via real PackageManager; installed native backends via real UDS. Desktop UI rendering is not included.",
|
||||
"passed":rows.len()-failed,"failed":failed,"temporaryDataRemoved":true,"results":rows,
|
||||
}));
|
||||
ensure!(failed == 0, "Remote source verification failed");
|
||||
|
||||
@@ -9,12 +9,11 @@ const { values } = parseArgs({ options: {
|
||||
'base-url': { type: 'string' },
|
||||
catalog: { type: 'string', default: 'source' },
|
||||
} });
|
||||
if (!values['base-url']) throw new Error('Usage: pnpm run release:prepare --base-url https://downloads.example/source/');
|
||||
const base = new URL(values['base-url']);
|
||||
if (base.protocol !== 'https:' || base.username || base.password || base.search || base.hash) {
|
||||
const base = values['base-url'] ? new URL(values['base-url']) : null;
|
||||
if (base && (base.protocol !== 'https:' || base.username || base.password || base.search || base.hash)) {
|
||||
throw new Error('Publication base must be an HTTPS directory URL without credentials, query or fragment');
|
||||
}
|
||||
if (!base.pathname.endsWith('/')) base.pathname += '/';
|
||||
if (base && !base.pathname.endsWith('/')) base.pathname += '/';
|
||||
const catalogDirectory = path.resolve(root, values.catalog);
|
||||
const publishDirectory = path.join(output, 'release');
|
||||
const timestamp = new Date().toISOString();
|
||||
@@ -41,18 +40,36 @@ for (const entry of entries) {
|
||||
const sha256 = createHash('sha256').update(bytes).digest('hex');
|
||||
if (sha256 !== entry.artifact.sha256 || bytes.length !== entry.artifact.size) throw new Error(`Build metadata doesn't match ${entry.file}; run pack again`);
|
||||
const relative = artifactPath(entry.manifest, entry.artifact.target);
|
||||
const url = new URL(relative.split('/').map(encodeURIComponent).join('/'), base).href;
|
||||
const url = base ? new URL(relative.split('/').map(encodeURIComponent).join('/'), base).href : relative;
|
||||
const spec = specifications[0];
|
||||
const artifact = { ...entry.artifact, url };
|
||||
catalogs.set(spec.file, appendRelease(catalogs.get(spec.file), entry.manifest, artifact, timestamp));
|
||||
files.push({ from: entry.file, relative, sha256, size: bytes.length, url });
|
||||
}
|
||||
// Git publication keeps immutable packages alongside the canonical index.
|
||||
if (!base) {
|
||||
for (const file of files) {
|
||||
try {
|
||||
const existing = await readFile(path.join(catalogDirectory, file.relative));
|
||||
if (existing.length !== file.size || createHash('sha256').update(existing).digest('hex') !== file.sha256) {
|
||||
throw new Error(`Published artifact differs: ${file.relative}; bump the plugin version`);
|
||||
}
|
||||
} catch (error) {
|
||||
if (error.code !== 'ENOENT') throw error;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Only the current approved publication plan belongs in the generated upload directory.
|
||||
await rm(publishDirectory, { recursive: true, force: true });
|
||||
for (const file of files) {
|
||||
const destination = path.join(publishDirectory, file.relative);
|
||||
await mkdir(path.dirname(destination), { recursive: true });
|
||||
await copyFile(file.from, destination);
|
||||
if (!base) {
|
||||
const published = path.join(catalogDirectory, file.relative);
|
||||
await mkdir(path.dirname(published), { recursive: true });
|
||||
await copyFile(file.from, published);
|
||||
}
|
||||
}
|
||||
await mkdir(catalogDirectory, { recursive: true });
|
||||
await mkdir(publishDirectory, { recursive: true });
|
||||
@@ -63,7 +80,7 @@ for (const [file, index] of catalogs) {
|
||||
await writeFile(temporary, text);
|
||||
await rename(temporary, path.join(catalogDirectory, file));
|
||||
}
|
||||
await writeFile(path.join(publishDirectory, 'upload-plan.json'), JSON.stringify({ baseUrl: base.href, files }, null, 2) + '\n');
|
||||
await writeFile(path.join(publishDirectory, 'upload-plan.json'), JSON.stringify({ kind: base ? 'http' : 'git', baseUrl: base?.href, files }, null, 2) + '\n');
|
||||
console.log(`Prepared ${files.length} immutable artifacts in ${publishDirectory}`);
|
||||
console.log(`Catalog history: ${catalogDirectory}`);
|
||||
console.log('Next: upload package files without overwrite, verify remote bytes, then atomically publish index.json.');
|
||||
console.log(base ? 'Next: upload package files without overwrite, verify remote bytes, then atomically publish index.json.' : 'Next: commit source/index.json and source/packages/ together, push the repository, then verify the Git source.');
|
||||
|
||||
+7
-2
@@ -9,7 +9,12 @@ import { assert, equal, expectError, hostChecks, backendChecks, sleep } from '..
|
||||
import { serve } from './serve.mjs';
|
||||
|
||||
// Only this optional integration adapter depends on the app checkout.
|
||||
const { values: args } = parseArgs({ options: { 'remote-source': { type: 'string' }, 'source-id': { type: 'string', default: 'trove-official' } } });
|
||||
const { values: args } = parseArgs({ options: {
|
||||
'remote-source': { type: 'string' },
|
||||
'source-id': { type: 'string', default: 'trove-official' },
|
||||
'source-kind': { type: 'string', default: 'http' },
|
||||
} });
|
||||
if (!['git', 'http'].includes(args['source-kind'])) throw new Error('--source-kind must be git or http');
|
||||
const reportPath = path.join(output, args['remote-source'] ? 'remote-verification.json' : 'verification.json');
|
||||
await rm(reportPath, { force: true });
|
||||
const app = process.env.TROVE_APP_DIR;
|
||||
@@ -41,7 +46,7 @@ const target = process.env.TROVE_QA_TARGET_DIR || path.join(output, 'host-target
|
||||
try {
|
||||
run('cargo', ['build', '--manifest-path', path.join(harness, 'Cargo.toml'), '--target-dir', target]);
|
||||
if (args['remote-source']) {
|
||||
const result = spawnSync(path.join(target, 'debug/trove-qa-host-check'), ['--remote-source', args['remote-source'], args['source-id']], { cwd: root, encoding: 'utf8', stdio: ['ignore', 'pipe', 'inherit'], timeout: 180000 });
|
||||
const result = spawnSync(path.join(target, 'debug/trove-qa-host-check'), ['--remote-source', args['remote-source'], args['source-id'], args['source-kind']], { cwd: root, encoding: 'utf8', stdio: ['ignore', 'pipe', 'inherit'], timeout: 180000 });
|
||||
if (result.error) throw result.error;
|
||||
const report = JSON.parse(result.stdout);
|
||||
report.generatedAt = new Date().toISOString();
|
||||
|
||||
Reference in New Issue
Block a user