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

Skip to content
Merged
Show file tree
Hide file tree
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
6 changes: 6 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,12 @@ WPK_PREFER_REGISTRY_VERSIONS=0
# The URL of the npm registry to use.
# REGISTRY_URL=https://registry.npmjs.org/

# Maximum duration (ms) allowed for npm installation during wpk init/create before failing.
# WPK_INIT_INSTALL_NODE_MAX_MS=180000

# Maximum duration (ms) allowed for composer installation during wpk init/create before failing.
# WPK_INIT_INSTALL_COMPOSER_MAX_MS=120000

# The import.meta.url of the CLI.
WPK_CLI_IMPORT_META_URL=

Expand Down
1 change: 0 additions & 1 deletion artifacts/storage-states/admin.json

This file was deleted.

3 changes: 3 additions & 0 deletions docs/.vitepress/critical-create-generate-failure.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ Capture deterministic timing metrics for installers and composer healers.
**Completion log.** Update after each run:

- [x] Pipeline adapter (65a) now routes init/create through `createInitPipeline` for staged logging.
- [x] Installers instrumented (65b) capturing npm/composer timings with env-configurable budgets.

#### 65a — Pipeline adapter for init/create

Expand Down Expand Up @@ -219,6 +220,8 @@ What to do:

- Mention the new env vars / budgets so CI (and the smoke harness) know how to raise or lower
tolerances when containers are slow.
- Current overrides: `WPK_INIT_INSTALL_NODE_MAX_MS` (default 180000ms) and
`WPK_INIT_INSTALL_COMPOSER_MAX_MS` (default 120000ms).
- Note that the timing metrics are now available in the pipeline artifact for Task 65d and the
packed workflow.

Expand Down
8 changes: 0 additions & 8 deletions docs/api/@wpkernel/cli/type-aliases/CreateCommandInstance.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,6 @@ Represents an instance of the `create` command.

## Type Declaration

### skipInstall

```ts
skipInstall: boolean;
```

Whether to skip dependency installation.

### target?

```ts
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import {
buildRouteMethodName,
deriveRouteSegments,
} from '../controller.routeNames';
import type { IRRoute, IRv1 } from '../../../ir/publicTypes';

function createIr(overrides: Partial<IRv1['meta']> = {}): IRv1 {
return {
meta: {
namespace: 'wpk/v1',
sanitizedNamespace: 'wpk/v1',
...overrides,
},
} as unknown as IRv1;
}

describe('controller route naming helpers', () => {
it('builds semantic method names by removing namespace segments', () => {
const ir = createIr();
const route = {
method: 'GET',
path: '/wpk/v1/jobs/:jobId',
} as IRRoute;

expect(buildRouteMethodName(route, ir)).toBe('getJobsJobId');
});

it('falls back to Route suffix when no path segments exist', () => {
const ir = createIr();
const route = {
method: 'POST',
path: '/',
} as IRRoute;

expect(buildRouteMethodName(route, ir)).toBe('postRoute');
});

it('derives segments when namespace does not match', () => {
const ir = createIr({
namespace: 'other/v2',
sanitizedNamespace: 'other/v2',
});
const segments = deriveRouteSegments('/wpk/v1/jobs/:id', ir);

expect(segments).toEqual(['wpk', 'v1', 'jobs', 'id']);
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
import {
buildStorageArtifacts,
resolveRouteMutationMetadata,
buildCacheKeyPlan,
} from '../controller.storageArtifacts';
import type { ResourceStorageHelperState } from '../types';
import type { IRResource } from '../../../ir';

jest.mock('@wpkernel/wp-json-ast', () => ({
buildResourceCacheKeysPlan: jest.fn().mockReturnValue('cache-plan'),
}));

const { buildResourceCacheKeysPlan } = jest.requireMock(
'@wpkernel/wp-json-ast'
);

function createState(): ResourceStorageHelperState {
return {
transient: new Map(),
wpOption: new Map(),
wpTaxonomy: new Map(),
};
}

function createResource(overrides: Partial<IRResource> = {}): IRResource {
return {
name: 'jobs',
cacheKeys: {
list: { segments: ['jobs'] },
get: { segments: ['jobs', ':id'] },
create: { segments: ['jobs', 'create'] },
update: { segments: ['jobs', 'update'] },
remove: { segments: ['jobs', 'remove'] },
},
...overrides,
} as unknown as IRResource;
}

describe('controller storage artifacts', () => {
it('returns taxonomy artifacts when state contains entries', () => {
const state = createState();
state.wpTaxonomy.set('jobs', {
helperMethods: ['taxonomyMethod'],
helperSignatures: ['taxonomySignature'],
routeHandlers: ['taxonomyHandler'] as unknown as never,
});

const artifacts = buildStorageArtifacts({
resource: createResource({ storage: { mode: 'wp-taxonomy' } }),
storageState: state,
});

expect(artifacts).toEqual({
helperMethods: ['taxonomyMethod'],
helperSignatures: ['taxonomySignature'],
routeHandlers: ['taxonomyHandler'],
});
});

it('returns transient artifacts and empty signatures when missing state', () => {
const state = createState();
state.transient.set('jobs', {
helperMethods: ['transientMethod'],
routeHandlers: ['transientHandler'] as unknown as never,
});

const artifacts = buildStorageArtifacts({
resource: createResource({ storage: { mode: 'transient' } }),
storageState: state,
});

expect(artifacts).toEqual({
helperMethods: ['transientMethod'],
helperSignatures: [],
transientHandlers: ['transientHandler'],
});
});

it('returns default artifacts when resource has no storage mode', () => {
const state = createState();
const artifacts = buildStorageArtifacts({
resource: createResource({ storage: undefined }),
storageState: state,
});

expect(artifacts).toEqual({
helperMethods: [],
helperSignatures: [],
});
});

it('resolves route mutation metadata for wp-post storage', () => {
expect(
resolveRouteMutationMetadata(
createResource({ storage: { mode: 'wp-post' } })
)
).toEqual({ channelTag: 'resource.wpPost.mutation' });
expect(
resolveRouteMutationMetadata(
createResource({ storage: { mode: 'transient' } })
)
).toBeUndefined();
});

it('builds cache key plan with optional segments removed when undefined', () => {
const resource = createResource({
cacheKeys: {
list: { segments: ['jobs'] },
get: { segments: ['jobs', ':id'] },
create: undefined,
update: undefined,
remove: undefined,
},
});

const result = buildCacheKeyPlan(resource);

expect(result).toBe('cache-plan');
expect(buildResourceCacheKeysPlan).toHaveBeenCalledWith({
list: { segments: ['jobs'] },
get: { segments: ['jobs', ':id'] },
});
});
});
97 changes: 27 additions & 70 deletions packages/cli/src/commands/__tests__/create.command.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { assignCommandContext } from '@wpkernel/test-utils/cli';
import { makeWorkspaceMock } from '../../../tests/workspace.test-support';
import { buildCreateCommand } from '../create';
import type { buildWorkspace } from '../../workspace/filesystem';
import type { InitWorkflowOptions } from '../init/types';

describe('CreateCommand', () => {
let loadWPKernelConfig: jest.Mock;
Expand Down Expand Up @@ -63,16 +64,24 @@ describe('CreateCommand', () => {
},
];

it('runs init workflow, readiness plan, and installs npm dependencies', async () => {
const workflow = jest.fn().mockResolvedValue({
manifest: { writes: [], deletes: [] },
summaryText: '[wpk] init created plugin scaffold for demo\n',
summaries: [],
dependencySource: 'fallback',
namespace: 'demo',
templateName: 'plugin',
});
it('runs init workflow, readiness plan, and configures installers', async () => {
let capturedWorkflowOptions: InitWorkflowOptions | undefined;
const workflow = jest
.fn()
.mockImplementation(async (options: InitWorkflowOptions) => {
capturedWorkflowOptions = options;
return {
manifest: { writes: [], deletes: [] },
summaryText:
'[wpk] init created plugin scaffold for demo\n',
summaries: [],
dependencySource: 'fallback',
namespace: 'demo',
templateName: 'plugin',
};
});
const npmInstall = jest.fn().mockResolvedValue(undefined);
const composerInstall = jest.fn().mockResolvedValue(undefined);
const ensureDirectory = jest.fn().mockResolvedValue(undefined);
const readinessRun = jest.fn().mockResolvedValue({ outcomes: [] });
let capturedContext: unknown;
Expand Down Expand Up @@ -101,6 +110,7 @@ describe('CreateCommand', () => {
ensureCleanDirectory: ensureDirectory,
buildReadinessRegistry: buildReadinessRegistry as never,
loadWPKernelConfig,
installComposerDependencies: composerInstall,
});

const command = new CreateCommand();
Expand All @@ -113,20 +123,14 @@ describe('CreateCommand', () => {
const exit = await command.execute();

expect(exit).toBe(WPK_EXIT_CODES.SUCCESS);
expect(workflow).toHaveBeenCalledWith(
expect.objectContaining({
workspace,
projectName: 'demo',
})
);
expect(workflow).toHaveBeenCalled();
expect(ensureDirectory).toHaveBeenCalledWith(
expect.objectContaining({
workspace,
directory: path.resolve(process.cwd(), 'demo'),
force: false,
})
);
expect(npmInstall).toHaveBeenCalledWith(workspace.root);
expect(readinessPlan).toHaveBeenCalledWith([
'workspace-hygiene',
'git',
Expand All @@ -142,60 +146,13 @@ describe('CreateCommand', () => {
.environment.allowDirty
).toBe(false);
expect(stdout.toString()).toContain('plugin scaffold');
});

it('skips installers when --skip-install is provided', async () => {
const workflow = jest.fn().mockResolvedValue({
manifest: { writes: [], deletes: [] },
summaryText: 'summary\n',
summaries: [],
dependencySource: 'fallback',
namespace: 'demo',
templateName: 'plugin',
});
const npmInstall = jest.fn().mockResolvedValue(undefined);
const readinessRun = jest.fn().mockResolvedValue({ outcomes: [] });
const readinessPlan = jest
.fn()
.mockImplementation((keys: string[]) => ({
keys,
run: readinessRun,
}));

const workspace = makeWorkspaceMock({ root: process.cwd() });

const CreateCommand = buildCreateCommand({
buildWorkspace: (() => workspace) as typeof buildWorkspace,
runWorkflow: workflow,
installNodeDependencies: npmInstall,
ensureCleanDirectory: jest.fn().mockResolvedValue(undefined),
buildReadinessRegistry: (() => ({
register: jest.fn(),
plan: readinessPlan,
describe: () => helperDescriptors,
})) as never,
loadWPKernelConfig,
});

const command = new CreateCommand();
command.skipInstall = true;
const { stdout } = assignCommandContext(command, {
cwd: process.cwd(),
});

const exit = await command.execute();

expect(exit).toBe(WPK_EXIT_CODES.SUCCESS);
expect(npmInstall).not.toHaveBeenCalled();
expect(readinessPlan).toHaveBeenCalledWith([
'workspace-hygiene',
'git',
'php-runtime',
'php-codemod-ingestion',
'php-printer-path',
]);
expect(readinessRun).toHaveBeenCalledTimes(1);
expect(stdout.toString()).toContain('summary');
expect(capturedWorkflowOptions?.installDependencies).toBe(true);
expect(
capturedWorkflowOptions?.installers?.installNodeDependencies
).toBe(npmInstall);
expect(
capturedWorkflowOptions?.installers?.installComposerDependencies
).toBe(composerInstall);
});

it('propagates --allow-dirty to readiness context', async () => {
Expand Down
Loading