Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
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
2 changes: 1 addition & 1 deletion .talismanrc
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
fileignoreconfig:
- filename: pnpm-lock.yaml
checksum: ac795ffbdc0f82463baf34715a30b76cf6a67b2d8893ec835a5861aa915b20bf
checksum: 78b7fca30ae03e2570a384c5432c10f0e6b023f492b68929795adcb4613e8673
version: '1.0'
10 changes: 5 additions & 5 deletions packages/contentstack-audit/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@contentstack/cli-audit",
"version": "2.0.0-beta.8",
"version": "2.0.0-beta.9",
"description": "Contentstack audit plugin",
"author": "Contentstack CLI",
"homepage": "https://github.com/contentstack/cli",
Expand All @@ -18,8 +18,8 @@
"/oclif.manifest.json"
],
"dependencies": {
"@contentstack/cli-command": "~2.0.0-beta.4",
"@contentstack/cli-utilities": "~2.0.0-beta.4",
"@contentstack/cli-command": "~2.0.0-beta.5",
"@contentstack/cli-utilities": "~2.0.0-beta.5",
"@oclif/core": "^4.3.0",
"@oclif/plugin-help": "^6.2.28",
"chalk": "^5.6.2",
Expand Down Expand Up @@ -73,8 +73,8 @@
"test": "mocha --forbid-only \"test/**/*.test.ts\"",
"version": "oclif readme && git add README.md",
"clean": "rm -rf ./lib ./node_modules tsconfig.tsbuildinfo oclif.manifest.json",
"test:unit:report": "nyc --extension .ts mocha --forbid-only \"test/unit/**/*.test.ts\"",
"test:unit": "mocha --timeout 10000 --forbid-only \"test/unit/**/*.test.ts\""
"test:unit:report": "nyc --extension .ts mocha --forbid-only --file test/unit/logger-config.js \"test/unit/**/*.test.ts\"",
"test:unit": "mocha --timeout 10000 --forbid-only --file test/unit/logger-config.js \"test/unit/**/*.test.ts\""
},
"engines": {
"node": ">=16"
Expand Down
22 changes: 16 additions & 6 deletions packages/contentstack-audit/src/modules/assets.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ export default class Assets extends BaseClass {
protected schema: ContentTypeStruct[] = [];
protected missingEnvLocales: Record<string, any> = {};
public moduleName: keyof typeof auditConfig.moduleConfig;
private fixOverwriteConfirmed: boolean | null = null;

constructor({ fix, config, moduleName }: ModuleConstructorParam & CtConstructorParam) {
super({ config });
Expand Down Expand Up @@ -161,11 +162,17 @@ export default class Assets extends BaseClass {

if (this.fix) {
log.debug('Fix mode enabled, checking write permissions', this.config.auditContext);
if (!this.config.flags['copy-dir'] && !this.config.flags['external-config']?.skipConfirm) {
log.debug(`Asking user for confirmation to write fix content (--yes flag: ${this.config.flags.yes})`, this.config.auditContext);
canWrite = this.config.flags.yes || (await cliux.confirm(commonMsg.FIX_CONFIRMATION));
if (this.config.flags['copy-dir'] || this.config.flags['external-config']?.skipConfirm || this.config.flags.yes) {
this.fixOverwriteConfirmed = true;
log.debug('Skipping confirmation due to copy-dir, external-config, or yes flags', this.config.auditContext);
} else if (this.fixOverwriteConfirmed !== null) {
canWrite = this.fixOverwriteConfirmed;
log.debug(`Using cached overwrite confirmation: ${canWrite}`, this.config.auditContext);
} else {
log.debug('Skipping confirmation due to copy-dir or external-config flags', this.config.auditContext);
log.debug(`Asking user for confirmation to write fix content (--yes flag: ${this.config.flags.yes})`, this.config.auditContext);
this.completeProgress(true);
canWrite = await cliux.confirm(commonMsg.FIX_CONFIRMATION);
this.fixOverwriteConfirmed = canWrite;
}

if (canWrite) {
Expand Down Expand Up @@ -248,13 +255,16 @@ export default class Assets extends BaseClass {
if (this.progressManager) {
this.progressManager.tick(true, `asset: ${assetUid}`, null);
}

if (this.fix) {
log.debug(`Fixing asset ${assetUid}`, this.config.auditContext);
log.info($t(auditFixMsg.ASSET_FIX, { uid: assetUid }), this.config.auditContext);
await this.writeFixContent(`${basePath}/${indexer[fileIndex]}`, this.assets);
}
}

if (this.fix) {
await this.writeFixContent(`${basePath}/${indexer[fileIndex]}`, this.assets);
}
}

log.debug(`Asset reference validation completed. Processed ${Object.keys(this.missingEnvLocales).length} assets with issues`, this.config.auditContext);
Expand Down
6 changes: 6 additions & 0 deletions packages/contentstack-audit/src/modules/content-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -222,9 +222,15 @@ export default class ContentType extends BaseClass {
let canWrite = true;

if (!this.inMemoryFix && this.fix) {
if (Array.isArray(this.schema) && this.schema.length === 0) {
log.debug('No schemas to write, skipping writeFixContent', this.config.auditContext);
return;
}

log.debug('Fix mode enabled, checking write permissions', this.config.auditContext);
if (!this.config.flags['copy-dir'] && !this.config.flags['external-config']?.skipConfirm) {
log.debug('Asking user for confirmation to write fix content', this.config.auditContext);
this.completeProgress(true);
canWrite = this.config.flags.yes ?? (await cliux.confirm(commonMsg.FIX_CONFIRMATION));
} else {
log.debug('Skipping confirmation due to copy-dir or external-config flags', this.config.auditContext);
Expand Down
1 change: 1 addition & 0 deletions packages/contentstack-audit/src/modules/custom-roles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -249,6 +249,7 @@ export default class CustomRoles extends BaseClass {
log.debug('Skipping confirmation due to copy-dir, external-config, or yes flags', this.config.auditContext);
} else {
log.debug('Asking user for confirmation to write fix content', this.config.auditContext);
this.completeProgress(true);
}

const canWrite = skipConfirm || (await cliux.confirm(commonMsg.FIX_CONFIRMATION));
Expand Down
16 changes: 12 additions & 4 deletions packages/contentstack-audit/src/modules/entries.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ export default class Entries extends BaseClass {
public environments: string[] = [];
public entryMetaData: Record<string, any>[] = [];
public moduleName: keyof typeof auditConfig.moduleConfig = 'entries';
private fixOverwriteConfirmed: boolean | null = null;

constructor({ fix, config, moduleName, ctSchema, gfSchema }: ModuleConstructorParam & CtConstructorParam) {
super({ config });
Expand Down Expand Up @@ -541,14 +542,21 @@ export default class Entries extends BaseClass {

const skipConfirm = this.config.flags['copy-dir'] || this.config.flags['external-config']?.skipConfirm;

if (skipConfirm) {
log.debug('Skipping confirmation due to copy-dir or external-config flags', this.config.auditContext);
let canWrite: boolean;
if (skipConfirm || this.config.flags.yes) {
canWrite = true;
this.fixOverwriteConfirmed = true;
log.debug('Skipping confirmation due to copy-dir, external-config, or yes flags', this.config.auditContext);
} else if (this.fixOverwriteConfirmed !== null) {
canWrite = this.fixOverwriteConfirmed;
log.debug(`Using cached overwrite confirmation: ${canWrite}`, this.config.auditContext);
} else {
log.debug('Asking user for confirmation to write fix content', this.config.auditContext);
this.completeProgress(true);
canWrite = await cliux.confirm(commonMsg.FIX_CONFIRMATION);
this.fixOverwriteConfirmed = canWrite;
}

const canWrite = skipConfirm || this.config.flags.yes || (await cliux.confirm(commonMsg.FIX_CONFIRMATION));

if (canWrite) {
log.debug(`Writing fixed entries to: ${filePath}`, this.config.auditContext);
writeFileSync(filePath, JSON.stringify(schema));
Expand Down
43 changes: 32 additions & 11 deletions packages/contentstack-audit/src/modules/extensions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -172,7 +172,19 @@ export default class Extensions extends BaseClass {
? JSON.parse(readFileSync(this.extensionsPath, 'utf8'))
: {};
log.debug(`Loaded ${Object.keys(newExtensionSchema).length} existing extensions`, this.config.auditContext);


let userConfirm: boolean;
if (
this.config.flags['copy-dir'] ||
this.config.flags['external-config']?.skipConfirm ||
this.config.flags.yes
) {
userConfirm = true;
} else {
this.completeProgress(true);
userConfirm = await cliux.confirm(commonMsg.FIX_CONFIRMATION);
}

for (const ext of missingCtInExtensions) {
const { uid, title } = ext;
log.debug(`Fixing extension: ${title} (${uid})`, this.config.auditContext);
Expand All @@ -187,8 +199,7 @@ export default class Extensions extends BaseClass {
} else {
log.debug(`Extension ${title} has no valid content types or scope not found`, this.config.auditContext);
cliux.print($t(commonMsg.EXTENSION_FIX_WARN, { title: title, uid }), { color: 'yellow' });
const shouldDelete = this.config.flags.yes || (await cliux.confirm(commonMsg.EXTENSION_FIX_CONFIRMATION));
if (shouldDelete) {
if (userConfirm) {
log.debug(`Deleting extension: ${title} (${uid})`, this.config.auditContext);
delete newExtensionSchema[uid];
} else {
Expand All @@ -198,23 +209,33 @@ export default class Extensions extends BaseClass {
}

log.debug(`Extensions scope fix completed, writing updated schema`, this.config.auditContext);
await this.writeFixContent(newExtensionSchema);
await this.writeFixContent(newExtensionSchema, userConfirm);
}

async writeFixContent(fixedExtensions: Record<string, Extension>) {
async writeFixContent(fixedExtensions: Record<string, Extension>, preConfirmed?: boolean) {
log.debug(`Writing fix content for ${Object.keys(fixedExtensions).length} extensions`, this.config.auditContext);
log.debug(`Fix mode: ${this.fix}`, this.config.auditContext);
log.debug(`Copy directory flag: ${this.config.flags['copy-dir']}`, this.config.auditContext);
log.debug(`External config skip confirm: ${this.config.flags['external-config']?.skipConfirm}`, this.config.auditContext);
log.debug(`Yes flag: ${this.config.flags.yes}`, this.config.auditContext);

if (
this.fix &&
(this.config.flags['copy-dir'] ||
this.config.flags['external-config']?.skipConfirm ||
this.config.flags.yes ||
(await cliux.confirm(commonMsg.FIX_CONFIRMATION)))
let shouldWrite: boolean;
if (!this.fix) {
shouldWrite = false;
} else if (preConfirmed !== undefined) {
shouldWrite = preConfirmed;
} else if (
this.config.flags['copy-dir'] ||
this.config.flags['external-config']?.skipConfirm ||
this.config.flags.yes
) {
shouldWrite = true;
} else {
this.completeProgress(true);
shouldWrite = await cliux.confirm(commonMsg.FIX_CONFIRMATION);
}

if (shouldWrite) {
const outputPath = join(this.folderPath, this.config.moduleConfig[this.moduleName].fileName);
log.debug(`Writing fixed extensions to: ${outputPath}`, this.config.auditContext);
log.debug(`Extensions to write: ${Object.keys(fixedExtensions).join(', ')}`, this.config.auditContext);
Expand Down
1 change: 1 addition & 0 deletions packages/contentstack-audit/src/modules/field_rules.ts
Original file line number Diff line number Diff line change
Expand Up @@ -474,6 +474,7 @@ export default class FieldRule extends BaseClass {
if (this.fix) {
if (!this.config.flags['copy-dir'] && !this.config.flags['external-config']?.skipConfirm) {
log.debug(`Asking user for confirmation to write fix content`, this.config.auditContext);
this.completeProgress(true);
canWrite = this.config.flags.yes ?? (await cliux.confirm(commonMsg.FIX_CONFIRMATION));
log.debug(`User confirmation: ${canWrite}`, this.config.auditContext);
} else {
Expand Down
45 changes: 35 additions & 10 deletions packages/contentstack-audit/src/modules/workflows.ts
Original file line number Diff line number Diff line change
Expand Up @@ -194,7 +194,22 @@ export default class Workflows extends BaseClass {

log.debug(`Loaded ${Object.keys(newWorkflowSchema).length} workflows for fixing`, this.config.auditContext);

if (Object.keys(newWorkflowSchema).length !== 0) {
const hasWorkflowsToFix = Object.keys(newWorkflowSchema).length !== 0;
let userConfirm: boolean;
if (!hasWorkflowsToFix) {
userConfirm = true;
} else if (
this.config.flags['copy-dir'] ||
this.config.flags['external-config']?.skipConfirm ||
this.config.flags.yes
) {
userConfirm = true;
} else {
this.completeProgress(true);
userConfirm = await cliux.confirm(commonMsg.FIX_CONFIRMATION);
}

if (hasWorkflowsToFix) {
log.debug(`Processing ${this.workflowSchema.length} workflows for fixes`, this.config.auditContext);

for (const workflow of this.workflowSchema) {
Expand Down Expand Up @@ -237,7 +252,7 @@ export default class Workflows extends BaseClass {

cliux.print(warningMessage, { color: 'yellow' });

if (this.config.flags.yes || (await cliux.confirm(commonMsg.WORKFLOW_FIX_CONFIRMATION))) {
if (userConfirm) {
log.debug(`Deleting workflow ${name} (${uid})`, this.config.auditContext);
delete newWorkflowSchema[workflow.uid];
} else {
Expand All @@ -250,24 +265,34 @@ export default class Workflows extends BaseClass {
}

log.debug(`Workflow schema fix completed`, this.config.auditContext);
await this.writeFixContent(newWorkflowSchema);
await this.writeFixContent(newWorkflowSchema, userConfirm);
}

async writeFixContent(newWorkflowSchema: Record<string, Workflow>) {
async writeFixContent(newWorkflowSchema: Record<string, Workflow>, preConfirmed?: boolean) {
log.debug(`Writing fix content`, this.config.auditContext);
log.debug(`Fix mode: ${this.fix}`, this.config.auditContext);
log.debug(`Copy directory flag: ${this.config.flags['copy-dir']}`, this.config.auditContext);
log.debug(`External config skip confirm: ${this.config.flags['external-config']?.skipConfirm}`, this.config.auditContext);
log.debug(`Yes flag: ${this.config.flags.yes}`, this.config.auditContext);
log.debug(`Workflows to write: ${Object.keys(newWorkflowSchema).length}`, this.config.auditContext);

if (
this.fix &&
(this.config.flags['copy-dir'] ||
this.config.flags['external-config']?.skipConfirm ||
this.config.flags.yes ||
(await cliux.confirm(commonMsg.FIX_CONFIRMATION)))
let shouldWrite: boolean;
if (!this.fix) {
shouldWrite = false;
} else if (preConfirmed !== undefined) {
shouldWrite = preConfirmed;
} else if (
this.config.flags['copy-dir'] ||
this.config.flags['external-config']?.skipConfirm ||
this.config.flags.yes
) {
shouldWrite = true;
} else {
this.completeProgress(true);
shouldWrite = await cliux.confirm(commonMsg.FIX_CONFIRMATION);
}

if (shouldWrite) {
const outputPath = join(this.folderPath, this.config.moduleConfig[this.moduleName].fileName);
log.debug(`Writing fixed workflows to: ${outputPath}`, this.config.auditContext);

Expand Down
49 changes: 48 additions & 1 deletion packages/contentstack-audit/test/unit/base-command.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import { resolve } from 'path';
import { fancy } from 'fancy-test';
import { expect } from 'chai';
import { FileTransportInstance } from 'winston/lib/winston/transports';

import { BaseCommand } from '../../src/base-command';
import { mockLogger } from './mock-logger';

Expand Down Expand Up @@ -168,4 +167,52 @@ describe('BaseCommand class', () => {
}
});
});

describe('init with external-config', () => {
class CMDCheckConfig extends BaseCommand<typeof CMDCheckConfig> {
async run() {
const sc = this.sharedConfig as Record<string, unknown>;
if (sc.testMergeKey !== undefined) this.log(String(sc.testMergeKey));
if (this.flags['external-config']?.noLog) this.log('noLog');
}
}

fancy
.stdout({ print: process.env.PRINT === 'true' || false })
.stub(winston.transports, 'File', () => fsTransport)
.stub(winston, 'createLogger', createMockWinstonLogger)
.stub(BaseCommand.prototype, 'parse', () =>
Promise.resolve({
args: {},
flags: { 'external-config': { config: { testMergeKey: 'merged' } } },
} as any)
)
.do(() => CMDCheckConfig.run([]))
.do((output: { stdout: string }) => expect(output.stdout).to.include('merged'))
.it('merges external-config.config into sharedConfig when present');

fancy
.stdout({ print: process.env.PRINT === 'true' || false })
.stub(winston.transports, 'File', () => fsTransport)
.stub(winston, 'createLogger', createMockWinstonLogger)
.stub(BaseCommand.prototype, 'parse', () =>
Promise.resolve({
args: {},
flags: { 'external-config': { noLog: true } },
} as any)
)
.do(() => CMDCheckConfig.run([]))
.do((output: { stdout: string }) => expect(output.stdout).to.include('noLog'))
.it('hits noLog branch when external-config.noLog is true');

fancy
.stdout({ print: process.env.PRINT === 'true' || false })
.stub(winston.transports, 'File', () => fsTransport)
.stub(winston, 'createLogger', createMockWinstonLogger)
.stub(BaseCommand.prototype, 'parse', () =>
Promise.resolve({ args: {}, flags: { 'external-config': {} } } as any)
)
.do(() => CMDCheckConfig.run([]))
.it('completes when external-config is empty (no merge, no noLog)');
});
});
12 changes: 12 additions & 0 deletions packages/contentstack-audit/test/unit/logger-config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
/**
* Loaded by Mocha via --file before any test. Forces log config to non-debug
* so the real Logger never enables the debug path and unit tests don't throw
* when user has run: csdx config:set:log --level debug
*/
const cliUtils = require('@contentstack/cli-utilities');
const configHandler = cliUtils.configHandler;
const originalGet = configHandler.get.bind(configHandler);
configHandler.get = function (key) {
if (key === 'log') return { level: 'info' };
return originalGet(key);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"entry-empty-title": { "title": "" },
"entry-no-title": {}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"1":"empty-entries.json"}
Loading
Loading