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
20 changes: 6 additions & 14 deletions src/commands/resource/resource-import-commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,20 +7,12 @@ import {
requireResourceValue,
} from './resource-workflow.js';
import {
formatLiferayResourceImportAdts,
getLiferayResourceImportAdtsExitCode,
formatLiferayResourceImportResult,
getLiferayResourceImportExitCode,
runLiferayResourceImportAdts,
} from '../../features/liferay/resource/liferay-resource-import-adts.js';
import {
formatLiferayResourceImportStructures,
getLiferayResourceImportStructuresExitCode,
runLiferayResourceImportStructures,
} from '../../features/liferay/resource/liferay-resource-import-structures.js';
import {
formatLiferayResourceImportTemplates,
getLiferayResourceImportTemplatesExitCode,
runLiferayResourceImportTemplates,
} from '../../features/liferay/resource/liferay-resource-import-templates.js';
} from '../../features/liferay/resource/liferay-resource-import-shared.js';
import {
formatLiferayResourceSyncAdt,
runLiferayResourceSyncAdt,
Expand Down Expand Up @@ -211,7 +203,7 @@ export function registerResourceImportCommands(resource: Command): void {
allowBreakingChange: Boolean(options.allowBreakingChange),
continueOnError: Boolean(options.continueOnError),
}),
render: {text: formatLiferayResourceImportStructures, exitCode: getLiferayResourceImportStructuresExitCode},
render: {text: formatLiferayResourceImportResult, exitCode: getLiferayResourceImportExitCode},
});

registerResourceWorkflow(resource, {
Expand Down Expand Up @@ -243,7 +235,7 @@ export function registerResourceImportCommands(resource: Command): void {
structureKey: options.structure as string | undefined,
continueOnError: Boolean(options.continueOnError),
}),
render: {text: formatLiferayResourceImportTemplates, exitCode: getLiferayResourceImportTemplatesExitCode},
render: {text: formatLiferayResourceImportResult, exitCode: getLiferayResourceImportExitCode},
});

registerResourceWorkflow(resource, {
Expand Down Expand Up @@ -277,6 +269,6 @@ export function registerResourceImportCommands(resource: Command): void {
createMissing: Boolean(options.createMissing),
continueOnError: Boolean(options.continueOnError),
}),
render: {text: formatLiferayResourceImportAdts, exitCode: getLiferayResourceImportAdtsExitCode},
render: {text: formatLiferayResourceImportResult, exitCode: getLiferayResourceImportExitCode},
});
}
29 changes: 24 additions & 5 deletions src/core/contracts/index.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,3 @@
/**
* Central export point for all Zod schemas and inferred types.
* Organized by surface: shared, inventory, resource.
*/

// Shared schemas (used by both inventory and resource)
export {
resolvedSiteSchema,
Expand Down Expand Up @@ -34,6 +29,17 @@ export {
liferayInventorySitesSchema,
liferayInventoryTemplatesSchema,
liferayInventoryStructuresSchema,
whereUsedResourceTypes,
whereUsedMatchKinds,
pageEvidenceSourceValues,
whereUsedResourceTypeSchema,
whereUsedMatchKindSchema,
pageEvidenceSourceSchema,
whereUsedQuerySchema,
whereUsedMatchSchema,
whereUsedPageMatchSchema,
whereUsedResultSchema,
whereUsedPlanResultSchema,
} from './inventory.schema.js';

export type {
Expand All @@ -43,6 +49,19 @@ export type {
LiferayInventorySites,
LiferayInventoryTemplates,
LiferayInventoryStructures,
WhereUsedResourceTypeValue,
WhereUsedMatchKindValue,
PageEvidenceSourceValue,
WhereUsedQuery,
WhereUsedResourceType,
WhereUsedMatchKind,
WhereUsedMatch,
WhereUsedPageMatch,
WhereUsedResultContract,
WhereUsedPlanResultContract,
WhereUsedResult,
WhereUsedPlanResult,
WhereUsedRunResult,
} from './inventory.schema.js';

// Resource schemas
Expand Down
185 changes: 159 additions & 26 deletions src/core/contracts/inventory.schema.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,7 @@
import {z} from 'zod';

/**
* Schemas for inventory surfaces (sites, templates, structures).
* These define normalized output types that commands return.
*/

/**
* LiferayInventorySite: normalized site info with group id, friendly URL, name, and pages command.
*/
// ── Site / Template / Structure inventory output ───────────────────────────────

export const liferayInventorySiteSchema = z.object({
groupId: z.number().int().positive(),
siteFriendlyUrl: z.string(),
Expand All @@ -17,9 +11,6 @@ export const liferayInventorySiteSchema = z.object({

export type LiferayInventorySite = z.infer<typeof liferayInventorySiteSchema>;

/**
* LiferayInventoryTemplate: normalized template info with id, name, structure ref, and optional script.
*/
export const liferayInventoryTemplateSchema = z.object({
id: z.string(),
name: z.string(),
Expand All @@ -30,9 +21,6 @@ export const liferayInventoryTemplateSchema = z.object({

export type LiferayInventoryTemplate = z.infer<typeof liferayInventoryTemplateSchema>;

/**
* LiferayInventoryStructure: minimal structure info with id, key, and name.
*/
export const liferayInventoryStructureSchema = z.object({
id: z.number().int(),
key: z.string(),
Expand All @@ -41,23 +29,168 @@ export const liferayInventoryStructureSchema = z.object({

export type LiferayInventoryStructure = z.infer<typeof liferayInventoryStructureSchema>;

/**
* LiferayInventorySites: array of normalized sites.
*/
export const liferayInventorySitesSchema = z.array(liferayInventorySiteSchema);

export type LiferayInventorySites = z.infer<typeof liferayInventorySitesSchema>;

/**
* LiferayInventoryTemplates: array of normalized templates.
*/
export const liferayInventoryTemplatesSchema = z.array(liferayInventoryTemplateSchema);

export type LiferayInventoryTemplates = z.infer<typeof liferayInventoryTemplatesSchema>;

/**
* LiferayInventoryStructures: array of normalized structures.
*/
export const liferayInventoryStructuresSchema = z.array(liferayInventoryStructureSchema);

export type LiferayInventoryStructures = z.infer<typeof liferayInventoryStructuresSchema>;

// ── Where-used: enum types (shared with page evidence) ────────────────────────

export const whereUsedResourceTypes = ['fragment', 'widget', 'portlet', 'structure', 'template', 'adt'] as const;

export const whereUsedMatchKinds = [
'fragmentEntry',
'widgetEntry',
'widgetAdt',
'portlet',
'journalArticleStructure',
'journalArticleTemplate',
'fragmentMappedStructure',
'fragmentMappedTemplate',
'contentStructure',
'displayPageArticle',
] as const;

export const pageEvidenceSourceValues = [
'fragmentEntryLink',
'portletLayout',
'journalArticle',
'renderedHtmlJournalContent',
'contentStructure',
'displayPageArticle',
] as const;

export const whereUsedResourceTypeSchema = z.enum(whereUsedResourceTypes);
export const whereUsedMatchKindSchema = z.enum(whereUsedMatchKinds);
export const pageEvidenceSourceSchema = z.enum(pageEvidenceSourceValues);

export type WhereUsedResourceTypeValue = (typeof whereUsedResourceTypes)[number];
export type WhereUsedMatchKindValue = (typeof whereUsedMatchKinds)[number];
export type PageEvidenceSourceValue = (typeof pageEvidenceSourceValues)[number];

// ── Where-used: output contract ────────────────────────────────────────────────

const whereUsedSiteOrderSchema = z.enum(['site', 'name', 'content']);

export const whereUsedQuerySchema = z.object({
type: whereUsedResourceTypeSchema,
keys: z.array(z.string()),
});

export const whereUsedMatchSchema = z.object({
resourceType: whereUsedResourceTypeSchema,
matchedKey: z.string(),
matchKind: whereUsedMatchKindSchema,
label: z.string(),
detail: z.string(),
source: pageEvidenceSourceSchema,
});

export const whereUsedPageMatchSchema = z.object({
pageType: z.enum(['regularPage', 'displayPage']),
pageName: z.string(),
friendlyUrl: z.string(),
fullUrl: z.string(),
viewUrl: z.string().optional(),
layoutId: z.number().optional(),
plid: z.number().optional(),
privateLayout: z.boolean(),
hidden: z.boolean().optional(),
editUrl: z.string().optional(),
matches: z.array(whereUsedMatchSchema),
});

const whereUsedSiteResultSchema = z.object({
siteFriendlyUrl: z.string(),
siteName: z.string(),
groupId: z.coerce.number(),
scannedPages: z.number(),
failedPages: z.number(),
matchedPages: z.array(whereUsedPageMatchSchema),
errors: z.array(z.object({fullUrl: z.string(), reason: z.string()})).optional(),
});

const whereUsedSkippedSiteSchema = z.object({
siteFriendlyUrl: z.string(),
groupId: z.coerce.number(),
reason: z.string(),
});

export const whereUsedResultSchema = z.object({
inventoryType: z.literal('whereUsed'),
query: z.object({
type: whereUsedResourceTypeSchema,
keys: z.array(z.string()),
}),
scope: z.object({
sites: z.array(z.string()),
includePrivate: z.boolean(),
concurrency: z.number(),
maxDepth: z.number(),
siteOrder: whereUsedSiteOrderSchema.default('site'),
siteLimit: z.number().optional(),
excludedSites: z.array(z.string()).default([]),
plan: z.literal(false).default(false),
}),
summary: z.object({
totalSites: z.number(),
totalScannedPages: z.number(),
totalMatchedPages: z.number(),
totalMatches: z.number(),
totalFailedPages: z.number(),
}),
sites: z.array(whereUsedSiteResultSchema),
skippedSites: z.array(whereUsedSkippedSiteSchema).optional(),
});

export const whereUsedPlanResultSchema = z.object({
inventoryType: z.literal('whereUsedPlan'),
query: z.object({
type: whereUsedResourceTypeSchema,
keys: z.array(z.string()),
}),
scope: z.object({
sites: z.array(z.string()),
includePrivate: z.boolean(),
concurrency: z.number(),
maxDepth: z.number(),
siteOrder: whereUsedSiteOrderSchema,
siteLimit: z.number().optional(),
excludedSites: z.array(z.string()),
plan: z.literal(true),
}),
summary: z.object({
totalSites: z.number(),
selectedSites: z.number(),
excludedSites: z.number(),
skippedSites: z.number(),
}),
sites: z.array(
z.object({
rank: z.number(),
siteFriendlyUrl: z.string(),
siteName: z.string(),
groupId: z.coerce.number(),
structuredContents: z.number().optional(),
selectionReason: z.enum(['explicitSite', 'siteOrder', 'contentOrder']),
}),
),
skippedSites: z.array(whereUsedSkippedSiteSchema).optional(),
});

export type WhereUsedResultContract = z.infer<typeof whereUsedResultSchema>;
export type WhereUsedPlanResultContract = z.infer<typeof whereUsedPlanResultSchema>;

// ── Where-used: derived public types ────────────────────────────────────────────
export type WhereUsedQuery = z.infer<typeof whereUsedQuerySchema>;
export type WhereUsedResourceType = WhereUsedResourceTypeValue;
export type WhereUsedMatchKind = WhereUsedMatchKindValue;
export type WhereUsedMatch = z.infer<typeof whereUsedMatchSchema>;
export type WhereUsedPageMatch = z.infer<typeof whereUsedPageMatchSchema>;
export type WhereUsedResult = WhereUsedResultContract;
export type WhereUsedPlanResult = WhereUsedPlanResultContract;
export type WhereUsedRunResult = WhereUsedResult | WhereUsedPlanResult;
71 changes: 0 additions & 71 deletions src/features/ai/ai-install-project.ts
Original file line number Diff line number Diff line change
Expand Up @@ -261,59 +261,6 @@ export async function installClaudeSkillCommands(
return installed;
}

export async function installProjectAgents(
targetDir: string,
assets: AiAssets,
projectType: ProjectType,
): Promise<string[]> {
const agentsDir = path.join(assets.projectDir, '.claude', 'agents');
if (!(await fs.pathExists(agentsDir))) {
return [];
}

const allowedAgents = await resolveProjectAgentNames(assets.projectDir, projectType);
if (allowedAgents.length === 0) {
return [];
}

const destinationDir = path.join(targetDir, '.claude', 'agents');
await fs.ensureDir(destinationDir);

const entries = await fs.readdir(agentsDir, {withFileTypes: true});
const agentFiles = entries.filter(
(e) => e.isFile() && e.name.endsWith('.md') && allowedAgents.includes(e.name.replace('.md', '')),
);
const installed: string[] = [];

for (const entry of agentFiles) {
const destination = path.join(destinationDir, entry.name);
if (await fs.pathExists(destination)) {
continue;
}
await copyAiTemplatePath(path.join(agentsDir, entry.name), destination);
installed.push(entry.name.replace('.md', ''));
}

return installed;
}

export function buildProjectOverlayWarnings(options: {
projectType: ProjectType;
projectSkillsInstalled: string[];
projectAgentsInstalled: string[];
}): string[] {
const warnings: string[] = [];

if (
options.projectAgentsInstalled.length > 0 &&
!options.projectSkillsInstalled.includes('project-issue-engineering')
) {
warnings.push('Some project Claude agents were installed without the expected project issue-engineering skill.');
}

return warnings;
}

export function buildWorkspaceCoexistenceWarnings(
projectType: ProjectType,
officialWorkspaceFilesDetected: string[],
Expand Down Expand Up @@ -347,24 +294,6 @@ export function resolveProjectSkillsManifest(projectDir: string, projectType: Pr
return path.join(projectDir, 'project-skills.txt');
}

export async function resolveProjectAgentNames(projectDir: string, projectType: ProjectType): Promise<string[]> {
const manifestPath = path.join(projectDir, `project-agents.${projectType}.txt`);
if (!(await fs.pathExists(manifestPath))) {
if (projectType === 'unknown') {
const unknownManifestPath = path.join(projectDir, 'project-agents.unknown.txt');
if (await fs.pathExists(unknownManifestPath)) {
return readSimpleManifest(unknownManifestPath);
}
}
if (projectType === 'ldev-native') {
return [];
}
return [];
}

return readSimpleManifest(manifestPath);
}

export async function readSimpleManifest(manifestPath: string): Promise<string[]> {
const content = await fs.readFile(manifestPath, 'utf8');
return content
Expand Down
Loading
Loading