mirror of
https://github.com/colbymchenry/codegraph.git
synced 2026-10-18 22:02:49 +08:00
feat: pin reviewed Drupal 0.1.1 while retaining immutable 0.1.0
This commit is contained in:
@@ -2,19 +2,36 @@
|
||||
"format": "codegraph-official-policy-1",
|
||||
"publisherId": "codegraph",
|
||||
"publisher": "CodeGraph",
|
||||
"releases": [{
|
||||
"id": "drupal",
|
||||
"version": "0.1.0",
|
||||
"packageName": "@colbymchenry/codegraph-drupal",
|
||||
"sha256": "e5015747d02fe50d6af607b6c44f1db9cf7969a0070866ed03c7643377a459c8",
|
||||
"bytes": 289167,
|
||||
"repository": "https://github.com/colbymchenry/codegraph",
|
||||
"sourceRevision": "37d4dd837120c1d56316c3cc65b6587b743a9112",
|
||||
"sourcePath": "extensions/drupal/index.cjs",
|
||||
"sourceSha256": "eb7688957a14edd36efd8c109283a7355ceb7dd8199987fe3264c3f37489e841",
|
||||
"entrySha256": "0e6d117bab017fd4b0a6f09fe2289bf42c4b597548228be244b5729f7a022dd8",
|
||||
"name": "Drupal",
|
||||
"description": "Follow Drupal routes, services, hooks, plugins and events through your codebase.",
|
||||
"readme": "Official CodeGraph Drupal extension preview. Routes and forms connect to their handlers; services connect to implementations and explicit injections; literal hooks and events connect to declared implementations.\n\nInstalling replaces the built-in Drupal resolver for this project. Requires CodeGraph >=1.6.0 <2 with extension support.\n\nComputed identifiers, missing external dependencies, ambiguous classes and unknown entity handlers remain unresolved. Broader Drupal coverage and performance review remain open."
|
||||
}]
|
||||
"releases": [
|
||||
{
|
||||
"id": "drupal",
|
||||
"version": "0.1.0",
|
||||
"packageName": "@colbymchenry/codegraph-drupal",
|
||||
"sha256": "e5015747d02fe50d6af607b6c44f1db9cf7969a0070866ed03c7643377a459c8",
|
||||
"bytes": 289167,
|
||||
"repository": "https://github.com/colbymchenry/codegraph",
|
||||
"sourceRevision": "37d4dd837120c1d56316c3cc65b6587b743a9112",
|
||||
"sourcePath": "extensions/drupal/index.cjs",
|
||||
"sourceSha256": "eb7688957a14edd36efd8c109283a7355ceb7dd8199987fe3264c3f37489e841",
|
||||
"entrySha256": "0e6d117bab017fd4b0a6f09fe2289bf42c4b597548228be244b5729f7a022dd8",
|
||||
"name": "Drupal",
|
||||
"description": "Follow Drupal routes, services, hooks, plugins and events through your codebase.",
|
||||
"readme": "Official CodeGraph Drupal extension preview. Routes and forms connect to their handlers; services connect to implementations and explicit injections; literal hooks and events connect to declared implementations.\n\nInstalling replaces the built-in Drupal resolver for this project. Requires CodeGraph >=1.6.0 <2 with extension support.\n\nComputed identifiers, missing external dependencies, ambiguous classes and unknown entity handlers remain unresolved. Broader Drupal coverage and performance review remain open."
|
||||
},
|
||||
{
|
||||
"id": "drupal",
|
||||
"version": "0.1.1",
|
||||
"packageName": "@colbymchenry/codegraph-drupal",
|
||||
"sha256": "e5ed73bee51524585e4f4da1d5017fdee3037a3fa71d8f08edab6c318fedc59a",
|
||||
"bytes": 291889,
|
||||
"repository": "https://github.com/colbymchenry/codegraph",
|
||||
"sourceRevision": "b27bf875c95d3b82a0df411f68d0d8497993667b",
|
||||
"sourcePath": "extensions/drupal/index.cjs",
|
||||
"sourceSha256": "e9a5f13baaf0ba53ab17f0e348edd73ba91c80a11034746372c808eda8e3714d",
|
||||
"entrySha256": "17aa1ea7feed33cb42e010d9eae15f4a07035987ced3b9cd0f6f39a2ee6adc91",
|
||||
"name": "Drupal",
|
||||
"description": "Follow Drupal routes, services, hooks, plugins and events through your codebase.",
|
||||
"readme": "Official CodeGraph Drupal extension 0.1.1 preview. Rejects commented/quoted declarations and handles explicit legacy and modern Symfony event argument order. Routes and forms connect to their handlers; services connect to implementations and explicit injections; literal hooks and events connect to declared implementations.\n\nInstalling replaces the built-in Drupal resolver for this project. Requires CodeGraph >=1.6.0 <2 with extension support.\n\nComputed identifiers, missing external dependencies, ambiguous classes and unknown entity handlers remain unresolved. Broader Drupal coverage and performance review remain open."
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -25,6 +25,7 @@ try{
|
||||
assert.throws(()=>makePlan(Buffer.concat([bytes,Buffer.from(' ')]),s.target,artifact),/integrity/);pass('tampered but valid JSON package rejected by reviewed provenance pin');
|
||||
await assert.rejects(()=>importOfficial(s.db,s.bucket,{...plan,listing:{...plan.listing,name:'forged'}},bytes),/plan changed/);pass('edited publication plan rejected');
|
||||
const wrong=makePlan(bytes,{...s.target,registryId:'0'.repeat(32)},artifact);await assert.rejects(()=>importOfficial(s.db,s.bucket,wrong,bytes),/identity/);pass('wrong registry identity rejected');
|
||||
const previousBytes=fs.readFileSync(path.join(root,'dist/extensions/drupal-0.1.0.cgext'));const previousPlan=makePlan(previousBytes,s.target,path.join(root,'dist/extensions/drupal-0.1.0.cgext'));assert.equal((await importOfficial(s.db,s.bucket,previousPlan,previousBytes)).status,'imported');assert.equal((await importOfficial(s.db,s.bucket,previousPlan,previousBytes)).status,'unchanged');pass('historical 0.1.0 remains immutable and importable alongside the reviewed current version');
|
||||
const result=await importOfficial(s.db,s.bucket,plan,bytes);assert.equal(result.status,'imported');assert.equal(result.listing.publisherId,'codegraph');assert.equal(result.listing.official,true);assert.deepEqual(Buffer.from(await(await s.bucket.get('packages/sha256/'+plan.listing.integrity)).arrayBuffer()),bytes);pass('official identity provenance and exact bytes published through real D1/R2');
|
||||
assert.equal((await importOfficial(s.db,s.bucket,makePlan(bytes,s.target,artifact),bytes)).status,'unchanged');pass('safe repeated plan is idempotent and retains original listing');
|
||||
const other=JSON.parse(bytes);other.package.codegraph.id='unofficial';other.package.name='unofficial';const community=Buffer.from(JSON.stringify(other));
|
||||
@@ -32,7 +33,7 @@ try{
|
||||
const normal=await submit(s,signed(community),201);assert.equal(normal.official,false);assert.notEqual(normal.publisherId,'codegraph');pass('public API rejects official identity spoof while preserving community publication');
|
||||
await submit(s,signed(bytes),400);pass('community cannot replace reserved official Drupal ownership');
|
||||
const owner=await servicesStart('owner-conflict');await submit(owner,signed(bytes),201);await assert.rejects(()=>importOfficial(owner.db,owner.bucket,makePlan(bytes,owner.target,artifact),bytes),/ownership conflict/);pass('prior community ownership is not stolen by operator import');await owner.close();
|
||||
const version=await servicesStart('version-conflict'),vp=makePlan(bytes,version.target,artifact);await version.db.prepare('INSERT INTO releases VALUES(?,?,?,?,?,?,?)').bind('drupal','0.1.0','codegraph',JSON.stringify({...vp.listing,name:'existing immutable listing'}),'packages/sha256/'+vp.listing.integrity,vp.listing.integrity,bytes.length).run();await assert.rejects(()=>importOfficial(version.db,version.bucket,vp,bytes),/version conflict/);pass('existing immutable version metadata conflict is not overwritten');await version.close();
|
||||
const version=await servicesStart('version-conflict'),vp=makePlan(bytes,version.target,artifact);await version.db.prepare('INSERT INTO releases VALUES(?,?,?,?,?,?,?)').bind('drupal',vp.listing.version,'codegraph',JSON.stringify({...vp.listing,name:'existing immutable listing'}),'packages/sha256/'+vp.listing.integrity,vp.listing.integrity,bytes.length).run();await assert.rejects(()=>importOfficial(version.db,version.bucket,vp,bytes),/version conflict/);pass('existing immutable version metadata conflict is not overwritten');await version.close();
|
||||
const race=await servicesStart('race'),rp=makePlan(bytes,race.target,artifact);const results=await Promise.all([importOfficial(race.db,race.bucket,rp,bytes),importOfficial(race.db,race.bucket,rp,bytes)]);assert.deepEqual(results.map(r=>r.status).sort(),['imported','unchanged']);pass('concurrent identical official imports converge to one release');await race.close();
|
||||
const failure=await servicesStart('failure'),fp=makePlan(bytes,failure.target,artifact);await assert.rejects(()=>importOfficial(failure.db,{put:async()=>{throw Error('R2 failure');}},fp,bytes),/R2 failure/);assert.equal(await failure.db.prepare('SELECT count(*) n FROM extensions').first('n'),0);
|
||||
await failure.db.exec("CREATE TRIGGER reject_official AFTER INSERT ON releases BEGIN SELECT RAISE(ABORT,'fixture D1 failure'); END;");await assert.rejects(()=>importOfficial(failure.db,failure.bucket,fp,bytes));assert.equal(await failure.db.prepare('SELECT count(*) n FROM extensions').first('n'),0);assert.equal(await failure.db.prepare('SELECT count(*) n FROM releases').first('n'),0);await failure.db.exec('DROP TRIGGER reject_official');assert.equal((await importOfficial(failure.db,failure.bucket,fp,bytes)).status,'imported');pass('R2 and D1 failures expose no release or orphan owner; exact retry succeeds');await failure.close();
|
||||
|
||||
@@ -11,6 +11,7 @@ const lab=fs.mkdtempSync(path.join(os.tmpdir(),'cg-official-browser-')),corpus=p
|
||||
const project=path.join(lab,'pathauto'),other=path.join(lab,'untouched'),state=path.join(lab,'registry'),artifact=path.join(root,'dist/extensions/drupal.cgext'),bytes=fs.readFileSync(artifact),checks=[],commands=[];
|
||||
fs.mkdirSync(project);fs.mkdirSync(other);fs.writeFileSync(path.join(other,'example.php'),'<?php function untouched() {}');
|
||||
cp.execFileSync('tar',['-x','-C',project],{input:cp.execFileSync('git',['-C',corpus,'archive',pin],{maxBuffer:64*1024*1024})});
|
||||
const extensionVersion=JSON.parse(bytes).package.version;
|
||||
const hash=b=>crypto.createHash('sha256').update(b).digest('hex');
|
||||
function snapshot(){const db=new DatabaseSync(path.join(project,'.codegraph/codegraph.db'),{readOnly:true});try{const nodes=db.prepare('SELECT * FROM nodes ORDER BY id').all().map(({updated_at,...n})=>n),edges=db.prepare('SELECT source,target,kind,metadata,line,col,provenance FROM edges ORDER BY source,target,kind,line,col,metadata').all();return {nodes,edges,pluginEdges:edges.filter(e=>JSON.parse(e.metadata||'{}').synthesizedBy==='drupal'),hash:hash(JSON.stringify({nodes,edges})),config:fs.existsSync(path.join(project,'codegraph.json'))?fs.readFileSync(path.join(project,'codegraph.json'),'utf8'):null};}finally{db.close();}}
|
||||
function verify(data){const definitions=[['/admin/config/search/path/settings','pathauto.routing.yml','buildForm','src/Form/PathautoSettingsForm.php','Drupal route handler'],['/admin/config/search/path/update_bulk','pathauto.routing.yml','buildForm','src/Form/PathautoBulkUpdateForm.php','Drupal route handler'],['getPunctuationCharacters','src/AliasCleaner.php','pathautoPunctuationCharsAlter','tests/modules/pathauto_custom_punctuation_test/src/Hook/PathautoCustomPunctuationTestHooks.php','Drupal hook pathauto_punctuation_chars_alter'],['PathautoGenerator','src/PathautoGenerator.php','AliasCleaner','src/AliasCleaner.php','Drupal service injection']];for(const [from,file,to,target,label]of definitions){const sources=new Set(data.nodes.filter(n=>n.name===from&&n.file_path===file).map(n=>n.id)),targets=new Set(data.nodes.filter(n=>n.name===to&&n.file_path===target).map(n=>n.id));assert.ok(data.pluginEdges.some(e=>sources.has(e.source)&&targets.has(e.target)&&JSON.parse(e.metadata).label===label),'Missing '+label+': '+from);}return definitions;}
|
||||
@@ -24,9 +25,9 @@ let server,bridge,browser,page;
|
||||
const g=await CodeGraph.init(project);try{const r=await g.indexAll();assert.equal(r.filesErrored,0);}finally{g.close();}assert.equal(snapshot().pluginEdges.length,0);
|
||||
bridge=await startExtensionBridge([other,project],origin);browser=await chromium.launch({executablePath:process.env.CHROME_PATH||'/opt/google/chrome/chrome',headless:true,args:['--no-sandbox']});page=await browser.newPage({viewport:{width:1440,height:1000}});page.setDefaultTimeout(60000);let popups=0;page.on('popup',()=>popups++);const pageErrors=[];page.on('pageerror',e=>pageErrors.push(e.message));
|
||||
await page.goto(bridge.connectionUrl);await page.getByRole('button',{name:'Official',exact:true}).click();await page.locator('.extension-card .official').waitFor();assert.equal(await page.locator('.extension-card').count(),1);assert.match(await page.locator('.extension-card .byline').innerText(),/CodeGraph.*OFFICIAL/s);pass('official catalog filter displays trusted CodeGraph Drupal identity');
|
||||
const popupReady=page.waitForEvent('popup');await page.locator('#connect').click();const popup=await popupReady;await popup.getByRole('button',{name:'Allow connection'}).click();await page.getByText('CodeGraph connected',{exact:true}).waitFor();await page.locator('#project').selectOption('1');await page.getByRole('link',{name:'Drupal',exact:true}).click();await page.locator('.compatibility').filter({hasText:'Selected 0.1.0'}).waitFor();assert.equal(await page.locator('.metadata dd.integrity').first().innerText(),'codegraph');await page.getByRole('button',{name:'Install extension'}).click();await page.waitForFunction(()=>state.snapshot?.jobId===1&&!state.snapshot.busy);assert.equal(await page.evaluate(()=>state.snapshot.error),undefined);assert.equal(popups,1);const installed=snapshot(),flows=verify(installed);assert.ok(!CodeGraph.isInitialized(other));pass('one connection and one Install refresh only selected pinned Drupal project; four real route hook and DI edges verified');
|
||||
const popupReady=page.waitForEvent('popup');await page.locator('#connect').click();const popup=await popupReady;await popup.getByRole('button',{name:'Allow connection'}).click();await page.getByText('CodeGraph connected',{exact:true}).waitFor();await page.locator('#project').selectOption('1');await page.getByRole('link',{name:'Drupal',exact:true}).click();await page.locator('.compatibility').filter({hasText:'Selected '+extensionVersion}).waitFor();assert.equal(await page.locator('.metadata dd.integrity').first().innerText(),'codegraph');await page.getByRole('button',{name:'Install extension'}).click();await page.waitForFunction(()=>state.snapshot?.jobId===1&&!state.snapshot.busy);assert.equal(await page.evaluate(()=>state.snapshot.error),undefined);assert.equal(popups,1);const installed=snapshot(),flows=verify(installed);assert.ok(!CodeGraph.isInitialized(other));pass('one connection and one Install refresh only selected pinned Drupal project; four real route hook and DI edges verified');
|
||||
fs.writeFileSync(path.join(out,'graph-proof.json'),JSON.stringify({corpus:'pathauto',revision:pin,artifactSHA:hash(bytes),nodes:installed.nodes.length,edges:installed.edges.length,pluginEdges:installed.pluginEdges.length,hash:installed.hash,expected:flows},null,2));await page.screenshot({path:path.join(out,'desktop-installed.png'),fullPage:true});
|
||||
const manager=new ExtensionManager(project),key='packages/sha256/'+hash(bytes);await server.bucket.put(key,Buffer.from('tampered'));await assert.rejects(()=>manager.installFromRegistry({registry:origin,id:'drupal',version:'0.1.0',replaces:['drupal']}));assert.equal(snapshot().hash,installed.hash);assert.equal(snapshot().config,installed.config);await server.bucket.put(key,bytes);pass('tampered registry artifact fails installation without changing existing graph or config');
|
||||
const manager=new ExtensionManager(project),key='packages/sha256/'+hash(bytes);await server.bucket.put(key,Buffer.from('tampered'));await assert.rejects(()=>manager.installFromRegistry({registry:origin,id:'drupal',version:extensionVersion,replaces:['drupal']}));assert.equal(snapshot().hash,installed.hash);assert.equal(snapshot().config,installed.config);await server.bucket.put(key,bytes);pass('tampered registry artifact fails installation without changing existing graph or config');
|
||||
const incompatible=JSON.parse(bytes);incompatible.package.codegraph.engines='>=9';await assert.rejects(()=>manager.install({bytes:Buffer.from(JSON.stringify(incompatible)),replaces:['drupal']}),/requires/);await assert.rejects(()=>manager.install({bytes:Buffer.from('{}'),replaces:['drupal']}));assert.equal(snapshot().hash,installed.hash);assert.equal(snapshot().config,installed.config);pass('incompatible and malformed installs preserve previously working Drupal graph');
|
||||
await page.getByRole('button',{name:'Disable',exact:true}).click();await page.getByRole('button',{name:'Enable',exact:true}).waitFor();assert.equal(snapshot().pluginEdges.length,0);pass('browser disable removes actual extension contributions');
|
||||
await page.getByRole('button',{name:'Enable',exact:true}).click();await page.getByRole('button',{name:'Disable',exact:true}).waitFor();verify(snapshot());await page.setViewportSize({width:390,height:844});await page.screenshot({path:path.join(out,'mobile-official.png'),fullPage:true});pass('browser enable restores expected graph and mobile official details');
|
||||
|
||||
@@ -5,6 +5,8 @@ const {CodeGraph}=require('../../dist');
|
||||
const {ExtensionManager}=require('../../dist/plugins/manager');
|
||||
Object.assign(process.env,{CODEGRAPH_TELEMETRY:'0',CODEGRAPH_PARSE_WORKERS:'2',CODEGRAPH_RESOLVE_WORKERS:'0',CODEGRAPH_SYNTH_TIMINGS:'all'});
|
||||
const [project,mode,operation,output,artifact='dist/extensions/drupal.cgext']=process.argv.slice(2);
|
||||
const sourceRevision=cp.execFileSync('git',['rev-parse','HEAD'],{encoding:'utf8'}).trim();
|
||||
const sourceDirty=!!cp.execFileSync('git',['status','--porcelain'],{encoding:'utf8'}).trim();
|
||||
const hash=b=>crypto.createHash('sha256').update(b).digest('hex');
|
||||
function graphSnapshot(){const db=new DatabaseSync(path.join(project,'.codegraph/codegraph.db'),{readOnly:true});try{
|
||||
const nodes=db.prepare('SELECT * FROM nodes ORDER BY id').all().map(({updated_at,...n})=>n);
|
||||
@@ -18,6 +20,6 @@ function graphSnapshot(){const db=new DatabaseSync(path.join(project,'.codegraph
|
||||
else {const graph=CodeGraph.isInitialized(project)?await CodeGraph.open(project):await CodeGraph.init(project);try{result=operation==='rebuild'?await graph.refreshPluginIndex():operation==='sync'?await graph.sync():await graph.indexAll();}finally{graph.close();}}
|
||||
assert.ok(!result?.filesErrored && result?.success!==false,JSON.stringify(result));
|
||||
const measured={wallMs:performance.now()-started,cpu:process.cpuUsage(cpu),resources:process.resourceUsage()};
|
||||
const record={revision:cp.execFileSync('git',['rev-parse','HEAD'],{encoding:'utf8'}).trim(),dirty:!!cp.execFileSync('git',['status','--porcelain'],{encoding:'utf8'}).trim(),project,mode,operation,artifactSHA:mode==='external'?hash(fs.readFileSync(artifact)):null,node:process.version,platform:process.platform,...measured,graph:graphSnapshot()};
|
||||
const record={revision:sourceRevision,dirty:sourceDirty,project,mode,operation,artifactSHA:mode==='external'?hash(fs.readFileSync(artifact)):null,node:process.version,platform:process.platform,...measured,graph:graphSnapshot()};
|
||||
fs.mkdirSync(path.dirname(output),{recursive:true});fs.writeFileSync(output,JSON.stringify(record,null,2));console.log(JSON.stringify(record));
|
||||
})().catch(e=>{console.error(e);process.exitCode=1;});
|
||||
|
||||
Reference in New Issue
Block a user