Skip to content
Draft
Show file tree
Hide file tree
Changes from 1 commit
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
4 changes: 4 additions & 0 deletions packages/agent-cli/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,10 @@ pnpm add -D @rsdoctor/agent-cli

The package exposes a binary named `rsdoctor-agent`.

## Artifact compatibility

The datasource accepts both legacy `{ data, clientRoutes }` artifacts and artifacts with the optional versioned top-level `metadata` field. For metadata-aware consumers, a section marked `collected` was collected even when its payload is empty; a section marked `omitted` retains the legacy placeholder or `undefined` payload and includes the reason it was not collected.

## Usage

```bash
Expand Down
8 changes: 8 additions & 0 deletions packages/agent-cli/src/commands/datasource.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,15 @@ interface RsdoctorError {
packages?: unknown[];
}

export interface RsdoctorArtifactMetadata {
schemaVersion: number;
[key: string]: unknown;
}

export interface RsdoctorData {
/** Absent on legacy artifacts; unknown fields are preserved for newer schemas. */
metadata?: RsdoctorArtifactMetadata;
clientRoutes?: string[];
data?: {
chunkGraph?: {
chunks?: Array<{
Expand Down
38 changes: 38 additions & 0 deletions packages/agent-cli/tests/rsdoctor-cli.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,44 @@ import {
import { runCli } from '../src/cli';

describe('rsdoctor cli tool executor', () => {
it('parses legacy and v1 artifacts without changing report data', () => {
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'agent-cli-data-'));
const legacyFile = path.join(tempDir, 'legacy.json');
const v1File = path.join(tempDir, 'v1.json');
const reportData = {
summary: { costs: [] },
moduleGraph: { modules: [], dependencies: [], exports: [] },
};
fs.writeFileSync(legacyFile, JSON.stringify({ data: reportData }));
fs.writeFileSync(
v1File,
JSON.stringify({
data: reportData,
metadata: {
schemaVersion: 1,
producer: { name: '@rsdoctor/core', version: '2.0.0-beta.0' },
futureField: { preserved: true },
},
}),
);

try {
const legacy = datasource.loadJsonData(legacyFile);
const v1 = datasource.loadJsonData(v1File);

expect(legacy.data).toEqual(reportData);
expect(legacy.metadata).toBeUndefined();
expect(v1.data).toEqual(reportData);
expect(v1.metadata).toEqual({
schemaVersion: 1,
producer: { name: '@rsdoctor/core', version: '2.0.0-beta.0' },
futureField: { preserved: true },
});
} finally {
fs.rmSync(tempDir, { recursive: true, force: true });
}
});

it('runs the mapped command and returns parsed json', async () => {
const commands: string[][] = [];
const catalog = getToolCatalog();
Expand Down
24 changes: 22 additions & 2 deletions packages/core/src/rspack-plugin/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -236,7 +236,7 @@ export class RsdoctorRspackPlugin<
...pluginTapPostOptions,
stage: pluginTapPostOptions.stage! + 100,
},
() => this.childDone(compiler, context),
(compilation) => this.childDone(compiler, context, compilation.hash),
);
} else {
compiler.hooks.afterPlugins.tap(pluginTapPostOptions, () =>
Expand All @@ -247,7 +247,7 @@ export class RsdoctorRspackPlugin<
...pluginTapPostOptions,
stage: pluginTapPostOptions.stage! + 100,
},
() => this.done(compiler, context),
(stats) => this.done(compiler, context, stats),
);
}

Expand Down Expand Up @@ -329,6 +329,7 @@ export class RsdoctorRspackPlugin<
public done = async (
compiler: Plugin.BaseCompilerType<'rspack'>,
context = this.getCompilerContext(compiler),
stats?: Plugin.BaseStats,
): Promise<void> => {
time('RsdoctorRspackPlugin.done');
try {
Expand All @@ -339,6 +340,7 @@ export class RsdoctorRspackPlugin<
context.sdk.addClientRoutes([
ManifestType.RsdoctorManifestClientRoutes.Overall,
]);
this.setArtifactBuildIdentity(compiler, context, stats?.hash);

if (context.sdk instanceof RsdoctorPrimarySDK) {
context.sdk.setOutputDir(
Expand Down Expand Up @@ -580,11 +582,13 @@ export class RsdoctorRspackPlugin<
private childDone = async (
compiler: Plugin.BaseCompilerType<'rspack'>,
context: RsdoctorCompilerContext,
compilationHash?: string | null,
): Promise<void> => {
await context.bootstrapTask;
context.sdk.addClientRoutes([
ManifestType.RsdoctorManifestClientRoutes.Overall,
]);
this.setArtifactBuildIdentity(compiler, context, compilationHash);
if (context.sdk instanceof RsdoctorPrimarySDK) {
context.sdk.setOutputDir(
context.sdk.parent.getCompilerOutputDir(context.sdk),
Expand All @@ -599,6 +603,22 @@ export class RsdoctorRspackPlugin<
}
};

private setArtifactBuildIdentity(
compiler: Plugin.BaseCompilerType<'rspack'>,
context: RsdoctorCompilerContext,
compilationHash?: string | null,
) {
const target = compiler.options.target;
const environment = compiler.name || compiler.options.name;
context.sdk.setArtifactBuildIdentity?.({
...(compilationHash ? { compilationHash } : {}),
...(typeof target === 'string' || Array.isArray(target)
? { target }
: {}),
...(environment ? { environment } : {}),
});
}

private shouldDisposeSDK() {
return (
this.options.disableClientServer ||
Expand Down
25 changes: 25 additions & 0 deletions packages/core/src/sdk/multiple/primary.ts
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@ export class RsdoctorPrimarySDK
if (cloudData && parent.isMultiple) {
cloudData.name = this.name;
cloudData.series = parent.getSeriesData();
cloudData.metadata = this.getArtifactMetadata('normal');
}

const result = await super.writeManifest();
Expand All @@ -112,9 +113,33 @@ export class RsdoctorPrimarySDK

this.cloudData.name = this.name;
this.cloudData.series = this.parent.getSeriesData();
this.cloudData.metadata = this.getArtifactMetadata('normal');
await super.writeManifest();
}

public getArtifactMetadata(
mode: Manifest.RsdoctorArtifactOutputMode,
storeData: Partial<SDK.BuilderStoreData> = this.getStoreData(),
): Manifest.RsdoctorArtifactMetadata {
const metadata = super.getArtifactMetadata(mode, storeData);
if (!this.parent.isMultiple) {
return metadata;
}

delete metadata.build.compilationHash;
delete metadata.build.target;
delete metadata.build.environment;
metadata.build.compilers = this.parent.getSeriesData().map((series) => {
const sdk = this.parent.slaves.find((item) => item.name === series.name)!;
return {
name: series.name,
stage: series.stage,
...sdk.getArtifactBuildIdentity(),
Comment thread
ScriptedAlchemy marked this conversation as resolved.
};
});
return metadata;
}

getSeriesData(serverUrl = false): Manifest.RsdoctorManifestSeriesData[] {
return this.parent.getSeriesData(serverUrl);
}
Expand Down
13 changes: 13 additions & 0 deletions packages/core/src/sdk/sdk/core.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,9 @@ export abstract class SDKCore<T extends RsdoctorSDKOptions>

protected _envinfo: SDK.EnvInfo = {} as SDK.EnvInfo;

protected _artifactBuildIdentity: Manifest.RsdoctorArtifactCompilationIdentity =
{};

private _clientRoutes: Set<Manifest.RsdoctorManifestClientRoutes> = new Set([
Manifest.RsdoctorManifestClientRoutes.Overall,
]);
Expand Down Expand Up @@ -108,6 +111,16 @@ export abstract class SDKCore<T extends RsdoctorSDKOptions>
return this.hash;
}

public setArtifactBuildIdentity(
identity: Manifest.RsdoctorArtifactCompilationIdentity,
) {
this._artifactBuildIdentity = { ...identity };
}

public getArtifactBuildIdentity() {
return { ...this._artifactBuildIdentity };
}

public getClientRoutes() {
return [...this._clientRoutes];
}
Expand Down
89 changes: 89 additions & 0 deletions packages/core/src/sdk/sdk/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import fs from 'node:fs';
import fse from 'fs-extra';
import path from 'path';
import { createRequire } from 'module';
import packageJson from '../../../package.json';
import { DevToolError } from '@/error';
import { Common, Constants, Manifest, SDK } from '@rsdoctor/shared/types';
import { RawSourceMap, SourceMapConsumer } from 'source-map';
Expand Down Expand Up @@ -386,6 +387,92 @@ export class RsdoctorSDK<
}
}

protected async writePieces(
storeData: Common.PlainObject,
options?: SDK.WriteStoreOptionsType,
) {
await super.writePieces(storeData, options);
if (this.cloudData) {
this.cloudData.metadata = this.getArtifactMetadata(
'normal',
storeData as Partial<SDK.BuilderStoreData>,
);
}
}

public getArtifactMetadata(
mode: Manifest.RsdoctorArtifactOutputMode,
storeData: Partial<SDK.BuilderStoreData> = this.getStoreData(),
): Manifest.RsdoctorArtifactMetadata {
const briefSections = this.extraConfig?.brief?.jsonOptions?.sections;
const isBriefJson =
mode === 'brief' && this.extraConfig?.brief?.type?.includes('json');
const compilerConfig = storeData.configs?.[0];
const buildIdentity = this.getArtifactBuildIdentity();
const sectionState = (
section: Manifest.RsdoctorArtifactSectionName,
): Manifest.RsdoctorArtifactSectionState =>
storeData[section] === undefined
? { status: 'omitted', reason: 'not-collected' }
: { status: 'collected' };

return {
schemaVersion: 1,
producer: {
name: '@rsdoctor/core',
version: packageJson.version,
},
output: { mode },
build: {
id: storeData.hash ?? this.getHash(),
root: storeData.root ?? this.root,
...buildIdentity,
compiler: {
name: this.name,
...(compilerConfig
? {
type: compilerConfig.name,
version: String(compilerConfig.version),
}
: {}),
},
},
sections: {
errors:
isBriefJson && briefSections && !briefSections.rules
? { status: 'omitted', reason: 'not-selected' }
: sectionState('errors'),
configs: sectionState('configs'),
summary: sectionState('summary'),
resolver: sectionState('resolver'),
Comment thread
ScriptedAlchemy marked this conversation as resolved.
Outdated
loader: sectionState('loader'),
Comment thread
ScriptedAlchemy marked this conversation as resolved.
Outdated
moduleGraph:
isBriefJson && briefSections && !briefSections.moduleGraph
? { status: 'omitted', reason: 'not-selected' }
: sectionState('moduleGraph'),
chunkGraph:
isBriefJson && briefSections && !briefSections.chunkGraph
? { status: 'omitted', reason: 'not-selected' }
: sectionState('chunkGraph'),
moduleCodeMap:
mode === 'brief'
? { status: 'omitted', reason: 'output-mode' }
: sectionState('moduleCodeMap'),
plugin: sectionState('plugin'),
packageGraph: this._packageGraph
? { status: 'collected' }
: { status: 'omitted', reason: 'not-collected' },
treeShaking:
mode === 'brief'
? { status: 'omitted', reason: 'output-mode' }
: this.extraConfig?.features?.treeShaking
? sectionState('treeShaking')
: { status: 'omitted', reason: 'feature-disabled' },
otherReports: sectionState('otherReports'),
},
};
}

public async writeStore(options?: SDK.WriteStoreOptionsType) {
logger.debug(`sdk.writeStore has run.`, '[SDK.writeStore][end]');
let htmlPath = '';
Expand All @@ -400,6 +487,7 @@ export class RsdoctorSDK<
const jsonData = {
data,
clientRoutes,
metadata: this.getArtifactMetadata('brief', data),
};

fs.mkdirSync(this.outputDir, { recursive: true });
Expand Down Expand Up @@ -538,6 +626,7 @@ export class RsdoctorSDK<

return t;
}, {} as Common.PlainObject) as unknown as Manifest.RsdoctorManifestWithShardingFiles['data'],
metadata: this.getArtifactMetadata('normal', dataValue),
__LOCAL__SERVER__: true,
__SOCKET__PORT__: this.server.socketUrl.port.toString(),
__SOCKET__URL__: this.server.socketUrl.socketUrl,
Expand Down
Loading
Loading