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
17 changes: 17 additions & 0 deletions .github/workflows/public-pr-validation.yml
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,23 @@ jobs:
- name: Run lint
run: npm run lint

- name: Run command outcome contract
run: npm run test:command-outcome-contract

- name: Run report fixture contract
run: npm run test:report-fixture-contract

- name: Run report lifecycle contract
run: npm run test:report-lifecycle-contract
- name: Run report lifecycle matrix
run: npm run test:report-lifecycle-matrix
- name: Run production desktop report contract
run: npm run test:production-report-desktop-contract
- name: Run production web report contract
run: npm run test:production-report-web-contract
- name: Run report mutation adequacy
run: npm run test:report-mutation-adequacy

- name: Run critical tests
run: npm run test:critical

Expand Down
2 changes: 1 addition & 1 deletion dist/chunks/advancedCache.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion dist/chunks/advancedCache.js.map

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions dist/chunks/analysis.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion dist/chunks/analysis.js.map

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions dist/chunks/decorationProvide.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion dist/chunks/decorationsAdvanced.js

Large diffs are not rendered by default.

6 changes: 3 additions & 3 deletions dist/chunks/extensionApi.js

Large diffs are not rendered by default.

6 changes: 3 additions & 3 deletions dist/chunks/fileDateProviderImpl.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion dist/chunks/fileDateProviderImpl.js.map

Large diffs are not rendered by default.

6 changes: 3 additions & 3 deletions dist/chunks/fileDateProviderImplExport.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion dist/chunks/fileDateProviderImplExport.js.map

Large diffs are not rendered by default.

6 changes: 3 additions & 3 deletions dist/chunks/providerInit.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion dist/chunks/providerInit.js.map

Large diffs are not rendered by default.

79 changes: 27 additions & 52 deletions dist/chunks/reporting.js

Large diffs are not rendered by default.

6 changes: 3 additions & 3 deletions dist/chunks/reporting.js.map

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion dist/chunks/runtimeManagement.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion dist/chunks/runtimeManagementHeavy.js

Large diffs are not rendered by default.

10 changes: 5 additions & 5 deletions dist/chunks/teamPersistence.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion dist/chunks/teamPersistence.js.map

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion dist/chunks/templates.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion dist/chunks/templates.js.map

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions dist/chunks/workspaceIntelligence.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion dist/chunks/workspaceIntelligence.js.map

Large diffs are not rendered by default.

58 changes: 29 additions & 29 deletions dist/extension.js

Large diffs are not rendered by default.

6 changes: 3 additions & 3 deletions dist/extension.js.map

Large diffs are not rendered by default.

62 changes: 31 additions & 31 deletions dist/extension.web.js

Large diffs are not rendered by default.

6 changes: 3 additions & 3 deletions dist/extension.web.js.map

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions dist/web-chunks/analysis.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion dist/web-chunks/analysis.js.map

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions dist/web-chunks/decorationProvide.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion dist/web-chunks/decorationsAdvanced.js

Large diffs are not rendered by default.

6 changes: 3 additions & 3 deletions dist/web-chunks/extensionApi.js

Large diffs are not rendered by default.

6 changes: 3 additions & 3 deletions dist/web-chunks/fileDateProviderImpl.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion dist/web-chunks/fileDateProviderImpl.js.map

Large diffs are not rendered by default.

6 changes: 3 additions & 3 deletions dist/web-chunks/fileDateProviderImplExport.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion dist/web-chunks/fileDateProviderImplExport.js.map

Large diffs are not rendered by default.

6 changes: 3 additions & 3 deletions dist/web-chunks/providerInit.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion dist/web-chunks/providerInit.js.map

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion dist/web-chunks/runtimeManagement.js

Large diffs are not rendered by default.

10 changes: 5 additions & 5 deletions dist/web-chunks/teamPersistence.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion dist/web-chunks/teamPersistence.js.map

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions dist/web-chunks/workspaceIntelligence.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion dist/web-chunks/workspaceIntelligence.js.map

Large diffs are not rendered by default.

25 changes: 22 additions & 3 deletions extension.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ const { fileSystem } = require('./src/filesystem/FileSystemAdapter');
const { registerCoreCommands } = require('./src/commands/coreCommands');
const { registerOnboardingCommands } = require('./src/commands/onboardingCommands');
const { registerMigrationCommands } = require('./src/commands/migrationCommands');
const { commandCancellation, commandEmpty, commandSuccess, isCommandOutcome, OUTCOME_CATEGORIES } = require('./src/utils/commandOutcome');
const { initializeTemplateStore } = require('./src/utils/templateStore');
const { SettingsMigrationManager } = require('./src/utils/settingsMigration');
const { ExtensionError, ERROR_CODES, ChunkLoadError, handleChunkFailure } = require('./src/utils/errors');
Expand Down Expand Up @@ -1282,11 +1283,15 @@ async function activate(context) {
context.subscriptions.push(saveTemplate);

// Register export/reporting commands
const recordedReportErrors = new WeakSet();
const generateReport = vscode.commands.registerCommand('explorerDates.generateReport', async () => {
recordCommandInvocation('explorerDates.generateReport');
try {
if (isWebEnvironment()) {
vscode.window.showInformationMessage('Export reporting is unavailable in VS Code for Web.');
return;
const outcome = commandEmpty();
recordCommandResult('explorerDates.generateReport', true, null, outcome.category);
return outcome.value;
}

// Workspace trust guard
Expand All @@ -1307,9 +1312,23 @@ async function activate(context) {
if (!reportingManager) {
raiseChunkUnavailable('Export reporting', 'exportReporting');
}
await reportingManager.showReportDialog();
logger.info('Report generation started');
const result = await reportingManager.showReportDialog();
const outcome = isCommandOutcome(result) ? result :
result === undefined ? commandCancellation() :
result === null ? commandEmpty() : commandSuccess(result);
const isFailure = outcome.category === OUTCOME_CATEGORIES.HANDLED_COMMAND_FAILURE || outcome.category === OUTCOME_CATEGORIES.UNHANDLED_COMMAND_FAILURE;
recordCommandResult('explorerDates.generateReport', !isFailure, outcome.error, outcome.category, outcome.errorContext);
if (isFailure) {
const error = outcome.error || new Error('Report generation failed');
if (error && (typeof error === 'object' || typeof error === 'function')) recordedReportErrors.add(error);
throw error;
}
logger.info('Report generation completed', { outcomeCategory: outcome.category });
return outcome.value;
} catch (error) {
if (!recordedReportErrors.has(error)) {
recordCommandResult('explorerDates.generateReport', false, error, OUTCOME_CATEGORIES.UNHANDLED_COMMAND_FAILURE);
}
logger.error('Failed to generate report', error);
vscode.window.showErrorMessage(l10n.getString('failedToGenerateReport', error.message));
throw error;
Expand Down
11 changes: 10 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -1137,8 +1137,17 @@
"icon": "icons/explorer-dates.png",
"scripts": {
"test:file-date-awareness-contract": "node tests/test-file-date-awareness-environment-contract.js",
"test:report-contract": "node -r ./tests/helpers/strictConsole.js tests/test-report-contract.js",
"test:command-outcome-contract": "node -r ./tests/helpers/strictConsole.js tests/test-command-outcome-contract.js",
"test:report-fixture-contract": "node -r ./tests/helpers/strictConsole.js tests/test-report-fixture-contract.js",
"test:report-lifecycle-contract": "node -r ./tests/helpers/strictConsole.js tests/test-report-lifecycle-contract.js",
"test:report-lifecycle-matrix": "node -r ./tests/helpers/strictConsole.js tests/test-report-lifecycle-matrix.js",
"test:production-report-desktop-contract": "node -r ./tests/helpers/strictConsole.js tests/test-production-report-contract.js",
"test:production-report-web-contract": "VSCODE_WEB=true node -r ./tests/helpers/strictConsole.js tests/test-production-report-contract.js",
"test:report-mutation-adequacy": "node -r ./tests/helpers/strictConsole.js tests/test-report-mutation-adequacy.js",
"test:extension-host-command-smoke": "xvfb-run -a -s '-screen 0 1280x800x24' node tests/run-extension-host-smoke.js",
"lint": "eslint .",
"test:critical": "npm run lint && npm run test:workflow-contracts && npm run test:freshness-policy-logging && npm run test:warning-filters && npm run test:config && npm run test:config-audit && npm run test:config-scenarios && npm run test:activation-api-export-contract",
"test:critical": "npm run lint && npm run test:command-outcome-contract && npm run test:report-fixture-contract && npm run test:report-lifecycle-contract && npm run test:report-lifecycle-matrix && npm run test:workflow-contracts && npm run test:freshness-policy-logging && npm run test:warning-filters && npm run test:config && npm run test:config-audit && npm run test:config-scenarios && npm run test:activation-api-export-contract",
"test:security-utils": "node -r ./tests/helpers/strictConsole.js tests/test-security-utils.js && node -r ./tests/helpers/strictConsole.js tests/test-security-allowed-paths-behavior.js",
"test": "npm run test:suite",
"test:suite": "npm run compile && npm run lint && npm run test:config && npm run test:config-scenarios && npm run test:feature-gates && npm run test:flows && npm run test:bundle && npm run test:verify-bundle && npm run test:extras && npm run test:performance",
Expand Down
46 changes: 35 additions & 11 deletions src/commands/coreCommands.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,14 @@
const vscode = require('vscode');
const { fileSystem } = require('../filesystem/FileSystemAdapter');
const { formatFileSize } = require('../utils/formatters');
const { normalizeStat, validDate } = require('../reporting/reportContract');
const {
commandEmpty,
commandFailure,
commandSuccess,
isCommandOutcome,
OUTCOME_CATEGORIES
} = require('../utils/commandOutcome');
const {
recordCommandRegistration,
recordCommandInvocation,
Expand All @@ -23,16 +32,27 @@ function loadChildProcess() {

function registerCoreCommands({ context, fileDateProvider, logger, l10n }) {
const subscriptions = [];
const recordedOutcomeErrors = new WeakSet();
const registerCommand = (commandId, handler) => {
recordCommandRegistration(commandId);
return vscode.commands.registerCommand(commandId, async (...args) => {
recordCommandInvocation(commandId);
try {
const result = await handler(...args);
recordCommandResult(commandId, true);
return result;
const outcome = isCommandOutcome(result) ? result : commandSuccess(result);
const isFailure = outcome.category === OUTCOME_CATEGORIES.HANDLED_COMMAND_FAILURE ||
outcome.category === OUTCOME_CATEGORIES.UNHANDLED_COMMAND_FAILURE;
recordCommandResult(commandId, !isFailure, outcome.error, outcome.category, outcome.errorContext);
if (isFailure) {
const error = outcome.error || new Error(`Command failed: ${commandId}`);
if (error && (typeof error === 'object' || typeof error === 'function')) recordedOutcomeErrors.add(error);
throw error;
}
return outcome.value;
} catch (error) {
recordCommandResult(commandId, false, error);
if (!recordedOutcomeErrors.has(error)) {
recordCommandResult(commandId, false, error, OUTCOME_CATEGORIES.UNHANDLED_COMMAND_FAILURE);
}
throw error;
}
});
Expand Down Expand Up @@ -222,26 +242,30 @@ function registerCoreCommands({ context, fileDateProvider, logger, l10n }) {
}
if (!targetUri) {
vscode.window.showWarningMessage('No file selected');
return;
return commandEmpty();
}

const stat = await fileSystem.stat(targetUri);
const fileName = getFileName(targetUri.fsPath || targetUri.path);
const fileSize = fileDateProvider?._formatFileSize(stat.size, 'auto') || `${stat.size} bytes`;
const modified = (stat.mtime instanceof Date ? stat.mtime : new Date(stat.mtime)).toLocaleString();
const created = (stat.birthtime instanceof Date ? stat.birthtime : new Date(stat.birthtime || stat.mtime)).toLocaleString();
const normalized = normalizeStat(stat);
const displayPath = targetUri.fsPath || targetUri.path || targetUri.toString();
const fileName = getFileName(displayPath);
const fileSize = normalized.size === null ? 'Unavailable' : formatFileSize(normalized.size, 'auto');
const modified = validDate(normalized.modified)?.toLocaleString() || 'Unavailable';
const created = validDate(normalized.created)?.toLocaleString() || 'Unavailable (not provided)';

const details = `File: ${fileName}\n` +
`Size: ${fileSize}\n` +
`Modified: ${modified}\n` +
`Created: ${created}\n` +
`Path: ${targetUri.fsPath || targetUri.path}`;
`Path: ${displayPath}`;

vscode.window.showInformationMessage(details, { modal: true });
logger.info(`File details shown for: ${targetUri.fsPath || targetUri.path}`);
logger.info(`File details shown for: ${displayPath}`);
return commandSuccess();
} catch (error) {
logger.error('Failed to show file details', error);
vscode.window.showErrorMessage(`Failed to show file details: ${error.message}`);
vscode.window.showErrorMessage(`Failed to show file details: ${error?.code === 'ENOENT' ? 'The selected resource no longer exists.' : 'The selected resource could not be read.'}`);
return commandFailure(error, { operation: 'show-file-details', errorCode: error?.code || null });
}
}));

Expand Down
Loading
Loading