Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit e93cbd1

Browse files
authored
Merge pull request #371 from theGeekist/review-php-json-ast-codemod-plan-document
2 parents 892f0e4 + 47ee3e4 commit e93cbd1

File tree

21 files changed

+1589
-3
lines changed

21 files changed

+1589
-3
lines changed

docs/.vitepress/critical-create-generate-failure.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -195,7 +195,9 @@ Align PHP asset packaging so runtime resolution matches the bundled tarball cont
195195

196196
**Probe.** Add a readiness helper that resolves `pretty-print.php` via the runtime path logic, failing with `EnvironmentalError(php.printerPath.mismatch)` when the path differs between source and tarball.【F:packages/cli/src/runtime/php.ts†L12-L139】
197197

198-
**Fix.** Update package exports and bundler configs so the helper passes in both environments. Cover with source and packed install tests.
198+
**Fix.** Update package exports and bundler configs so the helper passes in both environments. Cover with source and packed install tests. The CLI now includes a `php-codemod-ingestion` readiness helper that resolves `@wpkernel/php-json-ast/php/ingest-program.php` via the same ingestion runner used at runtime, failing fast when the tarball omits the script or points at mismatched paths.
199+
200+
`createPhpPrinterPathReadinessHelper` now aligns runtime and module resolution by probing canonical paths, raising `EnvironmentalError(php.printerPath.mismatch)` when they diverge. Unit coverage exercises missing runtime assets, resolver failures, and mismatched canonical paths to keep the readiness signal deterministic.【F:packages/cli/src/dx/readiness/helpers/phpPrinterPath.ts†L18-L142】【F:packages/cli/src/dx/readiness/helpers/**tests**/phpPrinterPath.test.ts†L1-L209】
199201

200202
`createPhpPrinterPathReadinessHelper` now aligns runtime and module resolution by probing canonical paths, raising `EnvironmentalError(php.printerPath.mismatch)` when they diverge. Unit coverage exercises missing runtime assets, resolver failures, and mismatched canonical paths to keep the readiness signal deterministic.【F:packages/cli/src/dx/readiness/helpers/phpPrinterPath.ts†L18-L142】【F:packages/cli/src/dx/readiness/helpers/**tests**/phpPrinterPath.test.ts†L1-L209】
201203

packages/cli/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@ cache invalidation and capability checks stay consistent.
7878
- The CLI threads PHP codemod configuration into `@wpkernel/php-json-ast` helpers; consult the
7979
[codemod plan](../../docs/internal/php-json-ast-codemod-plan.md) before enabling visitor stacks
8080
or diagnostics in new pipelines.
81+
- `adapters.php` may return a `codemods` manifest (`files`, optional `configurationPath`, diagnostics/driver overrides); the builder calls `createPhpCodemodIngestionHelper` to run `runPhpCodemodIngestion`, emit `.codemod.*` artefacts, and requeue the transformed programs before writing.
8182

8283
## Validation & test utilities
8384

packages/cli/bin/wpk.js

100644100755
File mode changed.

packages/cli/src/builders/php/__tests__/generate.integration.test.ts

Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,10 @@ import { buildWorkspace } from '../../../workspace';
88
import { withWorkspace } from '@wpkernel/test-utils/integration';
99
import { makePhpIrFixture } from '@wpkernel/test-utils/builders/php/resources.test-support';
1010
import * as phpDriver from '@wpkernel/php-driver';
11+
import {
12+
createBaselineCodemodConfiguration,
13+
serialisePhpCodemodConfiguration,
14+
} from '@wpkernel/php-json-ast';
1115
function normalisePhpValue(value: unknown): unknown {
1216
if (Array.isArray(value)) {
1317
return value.map(normalisePhpValue);
@@ -388,4 +392,123 @@ describe('createPhpBuilder integration', () => {
388392
},
389393
INTEGRATION_TIMEOUT_MS
390394
);
395+
396+
it(
397+
'ingests codemod targets declared via the PHP adapter configuration',
398+
async () => {
399+
const ir = makePhpIrFixture();
400+
ir.config.adapters = {
401+
php() {
402+
return {
403+
codemods: {
404+
files: ['plugin.php'],
405+
configurationPath: 'codemods/baseline.json',
406+
},
407+
};
408+
},
409+
};
410+
411+
const builder = createPhpBuilder();
412+
const reporter = createReporterStub();
413+
const queuedWrites: BuilderOutput['actions'] = [];
414+
const output: BuilderOutput = {
415+
actions: queuedWrites,
416+
queueWrite(action) {
417+
queuedWrites.push(action);
418+
},
419+
};
420+
421+
await withWorkspace(async (workspacePath) => {
422+
const workspace = buildWorkspace(workspacePath);
423+
await ensureCliVendorReady();
424+
await fs.cp(CLI_VENDOR_ROOT, workspace.resolve('vendor'), {
425+
recursive: true,
426+
});
427+
428+
const rootDir = path.resolve(__dirname, '../../../../../..');
429+
const beforeFixture = path.join(
430+
rootDir,
431+
'packages',
432+
'php-json-ast',
433+
'fixtures',
434+
'codemods',
435+
'BaselinePack.before.php'
436+
);
437+
const afterFixture = path.join(
438+
rootDir,
439+
'packages',
440+
'php-json-ast',
441+
'fixtures',
442+
'codemods',
443+
'BaselinePack.after.php'
444+
);
445+
446+
const configuration = createBaselineCodemodConfiguration();
447+
const configurationPath = workspace.resolve(
448+
'codemods',
449+
'baseline.json'
450+
);
451+
await fs.mkdir(path.dirname(configurationPath), {
452+
recursive: true,
453+
});
454+
await fs.writeFile(
455+
configurationPath,
456+
serialisePhpCodemodConfiguration(configuration)
457+
);
458+
459+
await fs.copyFile(
460+
beforeFixture,
461+
workspace.resolve('plugin.php')
462+
);
463+
464+
await builder.apply(
465+
{
466+
context: {
467+
workspace,
468+
reporter,
469+
phase: 'generate',
470+
},
471+
input: {
472+
phase: 'generate',
473+
options: {
474+
config: ir.config,
475+
namespace: ir.meta.namespace,
476+
origin: ir.meta.origin,
477+
sourcePath: ir.meta.sourcePath,
478+
},
479+
ir,
480+
},
481+
output,
482+
reporter,
483+
},
484+
undefined
485+
);
486+
487+
const generated = await fs.readFile(
488+
workspace.resolve('plugin.php'),
489+
'utf8'
490+
);
491+
const expected = await fs.readFile(afterFixture, 'utf8');
492+
expect(generated).toBe(expected);
493+
494+
const codemodSummary = await fs.readFile(
495+
workspace.resolve('plugin.php.codemod.summary.txt'),
496+
'utf8'
497+
);
498+
expect(codemodSummary).toContain('baseline');
499+
500+
const beforeAst = await fs.readFile(
501+
workspace.resolve('plugin.php.codemod.before.ast.json'),
502+
'utf8'
503+
);
504+
const afterAst = await fs.readFile(
505+
workspace.resolve('plugin.php.codemod.after.ast.json'),
506+
'utf8'
507+
);
508+
expect(beforeAst.trim()).not.toHaveLength(0);
509+
expect(afterAst.trim()).not.toHaveLength(0);
510+
});
511+
},
512+
INTEGRATION_TIMEOUT_MS
513+
);
391514
});
Lines changed: 221 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,221 @@
1+
import { createPhpBuilder } from '../pipeline.builder';
2+
import {
3+
createBuilderInput,
4+
createBuilderOutput,
5+
createMinimalIr,
6+
createPipelineContext,
7+
} from '../test-support/php-builder.test-support';
8+
9+
const codemodApplyMock = jest.fn(async (_options, next) => {
10+
await next?.();
11+
});
12+
const createCodemodHelperImpl = jest.fn(() => ({
13+
key: 'builder.generate.php.codemod-ingestion',
14+
kind: 'builder' as const,
15+
apply: codemodApplyMock,
16+
}));
17+
18+
const writerApplyMock = jest.fn(async (_options, next) => {
19+
await next?.();
20+
});
21+
const createWriterHelperImpl = jest.fn(() => ({
22+
key: 'builder.generate.php.writer',
23+
kind: 'builder' as const,
24+
apply: writerApplyMock,
25+
}));
26+
27+
jest.mock('../pipeline.codemods', () => ({
28+
createPhpCodemodIngestionHelper: jest.fn((options) =>
29+
createCodemodHelperImpl(options)
30+
),
31+
}));
32+
33+
jest.mock('@wpkernel/php-json-ast', () => {
34+
const actual = jest.requireActual('@wpkernel/php-json-ast');
35+
return {
36+
...actual,
37+
createPhpProgramWriterHelper: jest.fn((options) =>
38+
createWriterHelperImpl(options)
39+
),
40+
};
41+
});
42+
43+
const { createPhpCodemodIngestionHelper } = jest.requireMock(
44+
'../pipeline.codemods'
45+
) as {
46+
createPhpCodemodIngestionHelper: jest.Mock;
47+
};
48+
const { createPhpProgramWriterHelper } = jest.requireMock(
49+
'@wpkernel/php-json-ast'
50+
) as {
51+
createPhpProgramWriterHelper: jest.Mock;
52+
};
53+
54+
describe('createPhpBuilder - adapter codemods', () => {
55+
beforeEach(() => {
56+
jest.clearAllMocks();
57+
codemodApplyMock.mockImplementation(async (_options, next) => {
58+
await next?.();
59+
});
60+
writerApplyMock.mockImplementation(async (_options, next) => {
61+
await next?.();
62+
});
63+
});
64+
65+
it('threads adapter codemods through the helper pipeline', async () => {
66+
const builder = createPhpBuilder({
67+
driver: {
68+
binary: '/base/php',
69+
scriptPath: '/base/program-writer.php',
70+
importMetaUrl: 'file:///base/dist/index.js',
71+
},
72+
});
73+
74+
const ir = createMinimalIr({
75+
config: {
76+
adapters: {
77+
php() {
78+
return {
79+
driver: {
80+
scriptPath: '/adapter/program-writer.php',
81+
},
82+
codemods: {
83+
files: ['plugin.php', ''],
84+
configurationPath: 'codemods/baseline.json',
85+
diagnostics: { nodeDumps: true },
86+
driver: {
87+
binary: '/adapter/php',
88+
scriptPath: '/adapter/codemod.php',
89+
importMetaUrl:
90+
'file:///adapter/dist/index.js',
91+
},
92+
},
93+
};
94+
},
95+
},
96+
},
97+
});
98+
99+
const context = createPipelineContext();
100+
const input = createBuilderInput({
101+
ir,
102+
options: {
103+
config: ir.config,
104+
namespace: ir.config.namespace,
105+
},
106+
});
107+
const output = createBuilderOutput();
108+
109+
await builder.apply(
110+
{ context, input, output, reporter: context.reporter },
111+
undefined
112+
);
113+
114+
expect(createPhpCodemodIngestionHelper).toHaveBeenCalledTimes(1);
115+
expect(createCodemodHelperImpl).toHaveBeenCalledWith({
116+
files: ['plugin.php', ''],
117+
configurationPath: 'codemods/baseline.json',
118+
enableDiagnostics: true,
119+
phpBinary: '/adapter/php',
120+
scriptPath: '/adapter/codemod.php',
121+
importMetaUrl: 'file:///adapter/dist/index.js',
122+
});
123+
expect(createPhpProgramWriterHelper).toHaveBeenCalledTimes(1);
124+
expect(createWriterHelperImpl).toHaveBeenCalledWith({
125+
driver: {
126+
binary: '/base/php',
127+
scriptPath: '/adapter/program-writer.php',
128+
importMetaUrl: 'file:///base/dist/index.js',
129+
},
130+
});
131+
expect(codemodApplyMock).toHaveBeenCalled();
132+
expect(writerApplyMock).toHaveBeenCalled();
133+
});
134+
135+
it('skips codemod helper when no valid target files are declared', async () => {
136+
const builder = createPhpBuilder();
137+
138+
const ir = createMinimalIr({
139+
config: {
140+
adapters: {
141+
php() {
142+
return {
143+
codemods: {
144+
files: [123 as unknown as string],
145+
},
146+
};
147+
},
148+
},
149+
},
150+
});
151+
152+
const context = createPipelineContext();
153+
const input = createBuilderInput({
154+
ir,
155+
options: {
156+
config: ir.config,
157+
namespace: ir.config.namespace,
158+
},
159+
});
160+
const output = createBuilderOutput();
161+
162+
await builder.apply(
163+
{ context, input, output, reporter: context.reporter },
164+
undefined
165+
);
166+
167+
expect(createPhpCodemodIngestionHelper).not.toHaveBeenCalled();
168+
expect(createPhpProgramWriterHelper).toHaveBeenCalledTimes(1);
169+
});
170+
171+
it('defaults codemod driver overrides to the merged PHP driver options', async () => {
172+
const builder = createPhpBuilder({
173+
driver: {
174+
binary: '/base/php',
175+
scriptPath: '/base/codemod.php',
176+
importMetaUrl: 'file:///base/dist/index.js',
177+
},
178+
});
179+
180+
const ir = createMinimalIr({
181+
config: {
182+
adapters: {
183+
php() {
184+
return {
185+
codemods: {
186+
files: ['plugin.php'],
187+
driver: {
188+
// no overrides provided
189+
},
190+
},
191+
};
192+
},
193+
},
194+
},
195+
});
196+
197+
const context = createPipelineContext();
198+
const input = createBuilderInput({
199+
ir,
200+
options: {
201+
config: ir.config,
202+
namespace: ir.config.namespace,
203+
},
204+
});
205+
const output = createBuilderOutput();
206+
207+
await builder.apply(
208+
{ context, input, output, reporter: context.reporter },
209+
undefined
210+
);
211+
212+
expect(createPhpCodemodIngestionHelper).toHaveBeenCalledTimes(1);
213+
expect(createCodemodHelperImpl).toHaveBeenCalledWith(
214+
expect.objectContaining({
215+
phpBinary: '/base/php',
216+
scriptPath: '/base/codemod.php',
217+
importMetaUrl: 'file:///base/dist/index.js',
218+
})
219+
);
220+
});
221+
});

0 commit comments

Comments
 (0)