@contractual/cli 0.1.0-dev.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/bin/cli.js +2 -0
- package/dist/commands/breaking.command.d.ts +19 -0
- package/dist/commands/breaking.command.d.ts.map +1 -0
- package/dist/commands/breaking.command.js +138 -0
- package/dist/commands/changeset.command.d.ts +11 -0
- package/dist/commands/changeset.command.d.ts.map +1 -0
- package/dist/commands/changeset.command.js +65 -0
- package/dist/commands/contract.command.d.ts +34 -0
- package/dist/commands/contract.command.d.ts.map +1 -0
- package/dist/commands/contract.command.js +259 -0
- package/dist/commands/diff.command.d.ts +21 -0
- package/dist/commands/diff.command.d.ts.map +1 -0
- package/dist/commands/diff.command.js +58 -0
- package/dist/commands/index.d.ts +7 -0
- package/dist/commands/index.d.ts.map +1 -0
- package/dist/commands/index.js +6 -0
- package/dist/commands/init.command.d.ts +21 -0
- package/dist/commands/init.command.d.ts.map +1 -0
- package/dist/commands/init.command.js +294 -0
- package/dist/commands/lint.command.d.ts +16 -0
- package/dist/commands/lint.command.d.ts.map +1 -0
- package/dist/commands/lint.command.js +174 -0
- package/dist/commands/pre.command.d.ts +14 -0
- package/dist/commands/pre.command.d.ts.map +1 -0
- package/dist/commands/pre.command.js +141 -0
- package/dist/commands/status.command.d.ts +5 -0
- package/dist/commands/status.command.d.ts.map +1 -0
- package/dist/commands/status.command.js +120 -0
- package/dist/commands/version.command.d.ts +16 -0
- package/dist/commands/version.command.d.ts.map +1 -0
- package/dist/commands/version.command.js +247 -0
- package/dist/commands.d.ts +2 -0
- package/dist/commands.d.ts.map +1 -0
- package/dist/commands.js +84 -0
- package/dist/config/index.d.ts +3 -0
- package/dist/config/index.d.ts.map +1 -0
- package/dist/config/index.js +2 -0
- package/dist/config/loader.d.ts +28 -0
- package/dist/config/loader.d.ts.map +1 -0
- package/dist/config/loader.js +123 -0
- package/dist/config/schema.json +121 -0
- package/dist/config/validator.d.ts +28 -0
- package/dist/config/validator.d.ts.map +1 -0
- package/dist/config/validator.js +34 -0
- package/dist/core/diff.d.ts +26 -0
- package/dist/core/diff.d.ts.map +1 -0
- package/dist/core/diff.js +89 -0
- package/dist/formatters/diff.d.ts +31 -0
- package/dist/formatters/diff.d.ts.map +1 -0
- package/dist/formatters/diff.js +139 -0
- package/dist/governance/index.d.ts +11 -0
- package/dist/governance/index.d.ts.map +1 -0
- package/dist/governance/index.js +14 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +9 -0
- package/dist/utils/exec.d.ts +29 -0
- package/dist/utils/exec.d.ts.map +1 -0
- package/dist/utils/exec.js +66 -0
- package/dist/utils/files.d.ts +36 -0
- package/dist/utils/files.d.ts.map +1 -0
- package/dist/utils/files.js +137 -0
- package/dist/utils/index.d.ts +4 -0
- package/dist/utils/index.d.ts.map +1 -0
- package/dist/utils/index.js +3 -0
- package/dist/utils/output.d.ts +25 -0
- package/dist/utils/output.d.ts.map +1 -0
- package/dist/utils/output.js +73 -0
- package/dist/utils/prompts.d.ts +90 -0
- package/dist/utils/prompts.d.ts.map +1 -0
- package/dist/utils/prompts.js +119 -0
- package/package.json +81 -0
- package/src/config/schema.json +121 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { type PromptOptions } from '../utils/prompts.js';
|
|
2
|
+
import type { VersioningMode } from '@contractual/types';
|
|
3
|
+
/**
|
|
4
|
+
* Options for the init command
|
|
5
|
+
*/
|
|
6
|
+
interface InitOptions extends PromptOptions {
|
|
7
|
+
/** Initial version for contracts */
|
|
8
|
+
initialVersion?: string;
|
|
9
|
+
/** Versioning mode */
|
|
10
|
+
versioning?: VersioningMode;
|
|
11
|
+
/** Force reinitialize */
|
|
12
|
+
force?: boolean;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Initialize Contractual in a repository
|
|
16
|
+
*
|
|
17
|
+
* Scans for spec files and generates contractual.yaml configuration
|
|
18
|
+
*/
|
|
19
|
+
export declare function initCommand(options?: InitOptions): Promise<void>;
|
|
20
|
+
export {};
|
|
21
|
+
//# sourceMappingURL=init.command.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"init.command.d.ts","sourceRoot":"","sources":["../../src/commands/init.command.ts"],"names":[],"mappings":"AAaA,OAAO,EAML,KAAK,aAAa,EACnB,MAAM,qBAAqB,CAAC;AAC7B,OAAO,KAAK,EAAoC,cAAc,EAAE,MAAM,oBAAoB,CAAC;AAY3F;;GAEG;AACH,UAAU,WAAY,SAAQ,aAAa;IACzC,oCAAoC;IACpC,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,sBAAsB;IACtB,UAAU,CAAC,EAAE,cAAc,CAAC;IAC5B,yBAAyB;IACzB,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AAiGD;;;;GAIG;AACH,wBAAsB,WAAW,CAAC,OAAO,GAAE,WAAgB,GAAG,OAAO,CAAC,IAAI,CAAC,CAyJ1E"}
|
|
@@ -0,0 +1,294 @@
|
|
|
1
|
+
import { existsSync, writeFileSync, readFileSync } from 'node:fs';
|
|
2
|
+
import { join, basename } from 'node:path';
|
|
3
|
+
import fg from 'fast-glob';
|
|
4
|
+
import chalk from 'chalk';
|
|
5
|
+
import ora from 'ora';
|
|
6
|
+
import { parse as parseYaml, stringify as stringifyYaml } from 'yaml';
|
|
7
|
+
import { VersionManager } from '@contractual/changesets';
|
|
8
|
+
import { ensureContractualDir, detectSpecType, CONTRACTUAL_DIR, getSnapshotPath, } from '../utils/files.js';
|
|
9
|
+
import { promptSelect, promptVersion, promptConfirm, VERSION_CHOICES, VERSIONING_MODE_CHOICES, } from '../utils/prompts.js';
|
|
10
|
+
/**
|
|
11
|
+
* Default starting version for new contracts
|
|
12
|
+
*/
|
|
13
|
+
const DEFAULT_VERSION = '0.0.0';
|
|
14
|
+
/**
|
|
15
|
+
* Default versioning mode
|
|
16
|
+
*/
|
|
17
|
+
const DEFAULT_VERSIONING_MODE = 'independent';
|
|
18
|
+
/**
|
|
19
|
+
* Glob patterns to find spec files
|
|
20
|
+
*/
|
|
21
|
+
const SPEC_PATTERNS = [
|
|
22
|
+
'**/*.openapi.yaml',
|
|
23
|
+
'**/*.openapi.yml',
|
|
24
|
+
'**/*.openapi.json',
|
|
25
|
+
'**/openapi.yaml',
|
|
26
|
+
'**/openapi.yml',
|
|
27
|
+
'**/openapi.json',
|
|
28
|
+
'**/*.asyncapi.yaml',
|
|
29
|
+
'**/*.asyncapi.yml',
|
|
30
|
+
'**/*.asyncapi.json',
|
|
31
|
+
'**/*.schema.json',
|
|
32
|
+
'**/*.odcs.yaml',
|
|
33
|
+
'**/*.odcs.yml',
|
|
34
|
+
];
|
|
35
|
+
/**
|
|
36
|
+
* Directories to ignore when scanning
|
|
37
|
+
*/
|
|
38
|
+
const IGNORE_PATTERNS = [
|
|
39
|
+
'**/node_modules/**',
|
|
40
|
+
'**/.git/**',
|
|
41
|
+
'**/dist/**',
|
|
42
|
+
'**/build/**',
|
|
43
|
+
'**/.contractual/**',
|
|
44
|
+
];
|
|
45
|
+
/**
|
|
46
|
+
* Extract contract name from file path
|
|
47
|
+
* Uses the filename stem without extension patterns
|
|
48
|
+
*/
|
|
49
|
+
function extractContractName(filePath) {
|
|
50
|
+
const base = basename(filePath);
|
|
51
|
+
// Remove known suffixes and extensions
|
|
52
|
+
let name = base
|
|
53
|
+
.replace(/\.openapi\.(ya?ml|json)$/i, '')
|
|
54
|
+
.replace(/\.asyncapi\.(ya?ml|json)$/i, '')
|
|
55
|
+
.replace(/\.schema\.json$/i, '')
|
|
56
|
+
.replace(/\.odcs\.ya?ml$/i, '')
|
|
57
|
+
.replace(/\.(ya?ml|json)$/i, '');
|
|
58
|
+
// Handle generic names like "openapi" -> use parent directory name
|
|
59
|
+
if (['openapi', 'asyncapi', 'schema', 'spec', 'api'].includes(name.toLowerCase())) {
|
|
60
|
+
const parts = filePath.split('/');
|
|
61
|
+
if (parts.length >= 2) {
|
|
62
|
+
name = parts[parts.length - 2];
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
return name;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Get initial version through prompts or options
|
|
69
|
+
*/
|
|
70
|
+
async function getInitialVersion(options) {
|
|
71
|
+
// If version provided via CLI, use it
|
|
72
|
+
if (options.initialVersion) {
|
|
73
|
+
return options.initialVersion;
|
|
74
|
+
}
|
|
75
|
+
// Prompt for version
|
|
76
|
+
const versionChoice = await promptSelect('Initial version for contracts:', [...VERSION_CHOICES], '0.0.0', options);
|
|
77
|
+
if (versionChoice === 'custom') {
|
|
78
|
+
return promptVersion('Enter version:', DEFAULT_VERSION, options);
|
|
79
|
+
}
|
|
80
|
+
return versionChoice;
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Get versioning mode through prompts or options
|
|
84
|
+
*/
|
|
85
|
+
async function getVersioningMode(options) {
|
|
86
|
+
if (options.versioning) {
|
|
87
|
+
return options.versioning;
|
|
88
|
+
}
|
|
89
|
+
return promptSelect('Versioning mode:', [...VERSIONING_MODE_CHOICES], DEFAULT_VERSIONING_MODE, options);
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Initialize Contractual in a repository
|
|
93
|
+
*
|
|
94
|
+
* Scans for spec files and generates contractual.yaml configuration
|
|
95
|
+
*/
|
|
96
|
+
export async function initCommand(options = {}) {
|
|
97
|
+
const cwd = process.cwd();
|
|
98
|
+
const configPath = join(cwd, 'contractual.yaml');
|
|
99
|
+
const contractualDir = join(cwd, CONTRACTUAL_DIR);
|
|
100
|
+
// Check if already initialized
|
|
101
|
+
if (existsSync(configPath) && !options.force) {
|
|
102
|
+
// Try to handle existing project with uninitialized contracts
|
|
103
|
+
await handleExistingProject(cwd, configPath, contractualDir, options);
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
const spinner = ora('Scanning for spec files...').start();
|
|
107
|
+
try {
|
|
108
|
+
// Scan for spec files
|
|
109
|
+
const files = await fg(SPEC_PATTERNS, {
|
|
110
|
+
cwd,
|
|
111
|
+
ignore: IGNORE_PATTERNS,
|
|
112
|
+
absolute: false,
|
|
113
|
+
onlyFiles: true,
|
|
114
|
+
});
|
|
115
|
+
spinner.succeed(`Found ${files.length} potential spec file(s)`);
|
|
116
|
+
// Build contract definitions
|
|
117
|
+
const contracts = [];
|
|
118
|
+
const seenNames = new Set();
|
|
119
|
+
for (const filePath of files) {
|
|
120
|
+
const absolutePath = join(cwd, filePath);
|
|
121
|
+
const detectedType = detectSpecType(absolutePath);
|
|
122
|
+
if (!detectedType) {
|
|
123
|
+
continue;
|
|
124
|
+
}
|
|
125
|
+
let name = extractContractName(filePath);
|
|
126
|
+
// Ensure unique names
|
|
127
|
+
if (seenNames.has(name)) {
|
|
128
|
+
let counter = 2;
|
|
129
|
+
while (seenNames.has(`${name}-${counter}`)) {
|
|
130
|
+
counter++;
|
|
131
|
+
}
|
|
132
|
+
name = `${name}-${counter}`;
|
|
133
|
+
}
|
|
134
|
+
seenNames.add(name);
|
|
135
|
+
contracts.push({
|
|
136
|
+
name,
|
|
137
|
+
type: detectedType,
|
|
138
|
+
path: filePath,
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
if (contracts.length === 0) {
|
|
142
|
+
console.log(chalk.yellow('\nNo spec files found'));
|
|
143
|
+
console.log(chalk.dim('\nSupported file patterns:'));
|
|
144
|
+
console.log(chalk.dim(' - *.openapi.yaml/json'));
|
|
145
|
+
console.log(chalk.dim(' - *.asyncapi.yaml/json'));
|
|
146
|
+
console.log(chalk.dim(' - *.schema.json'));
|
|
147
|
+
console.log(chalk.dim(' - *.odcs.yaml'));
|
|
148
|
+
console.log(chalk.dim('\nYou can manually create contractual.yaml to define contracts.'));
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
// Show found contracts
|
|
152
|
+
console.log();
|
|
153
|
+
for (const contract of contracts) {
|
|
154
|
+
const typeColor = getTypeColor(contract.type);
|
|
155
|
+
console.log(` ${chalk.dim('Found:')} ${contract.path} ${chalk.dim('(')}${typeColor(contract.type)}${chalk.dim(')')}`);
|
|
156
|
+
}
|
|
157
|
+
console.log();
|
|
158
|
+
// Get version and mode through prompts
|
|
159
|
+
const initialVersion = await getInitialVersion(options);
|
|
160
|
+
const versioningMode = await getVersioningMode(options);
|
|
161
|
+
// Generate config
|
|
162
|
+
const config = {
|
|
163
|
+
contracts,
|
|
164
|
+
changeset: {
|
|
165
|
+
autoDetect: true,
|
|
166
|
+
requireOnPR: true,
|
|
167
|
+
},
|
|
168
|
+
};
|
|
169
|
+
// Only add versioning section if not using defaults
|
|
170
|
+
if (versioningMode !== 'independent') {
|
|
171
|
+
config.versioning = {
|
|
172
|
+
mode: versioningMode,
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
// Write contractual.yaml
|
|
176
|
+
const yamlContent = stringifyYaml(config, {
|
|
177
|
+
lineWidth: 100,
|
|
178
|
+
singleQuote: true,
|
|
179
|
+
});
|
|
180
|
+
writeFileSync(configPath, yamlContent, 'utf-8');
|
|
181
|
+
// Create .contractual directory structure
|
|
182
|
+
const createdDir = ensureContractualDir(cwd);
|
|
183
|
+
// Create snapshots and set initial versions
|
|
184
|
+
const versionManager = new VersionManager(createdDir);
|
|
185
|
+
for (const contract of contracts) {
|
|
186
|
+
const absolutePath = join(cwd, contract.path);
|
|
187
|
+
versionManager.setVersion(contract.name, initialVersion, absolutePath);
|
|
188
|
+
}
|
|
189
|
+
// Print summary
|
|
190
|
+
console.log();
|
|
191
|
+
console.log(chalk.green('✓') + ' Initialized Contractual');
|
|
192
|
+
console.log();
|
|
193
|
+
console.log(chalk.bold('Created:'));
|
|
194
|
+
console.log(` ${chalk.green('+')} contractual.yaml`);
|
|
195
|
+
console.log(` ${chalk.green('+')} .contractual/`);
|
|
196
|
+
console.log(` ${chalk.green('+')} .contractual/changesets/`);
|
|
197
|
+
console.log(` ${chalk.green('+')} .contractual/snapshots/`);
|
|
198
|
+
console.log(` ${chalk.green('+')} .contractual/versions.json`);
|
|
199
|
+
console.log();
|
|
200
|
+
console.log(chalk.bold(`Detected ${contracts.length} contract(s) at v${initialVersion}:`));
|
|
201
|
+
for (const contract of contracts) {
|
|
202
|
+
const typeColor = getTypeColor(contract.type);
|
|
203
|
+
console.log(` ${chalk.cyan(contract.name)} ${chalk.dim('(')}${typeColor(contract.type)}${chalk.dim(')')}`);
|
|
204
|
+
console.log(` ${chalk.dim(contract.path)}`);
|
|
205
|
+
}
|
|
206
|
+
if (versioningMode !== 'independent') {
|
|
207
|
+
console.log();
|
|
208
|
+
console.log(chalk.dim(`Versioning mode: ${versioningMode}`));
|
|
209
|
+
}
|
|
210
|
+
console.log();
|
|
211
|
+
console.log(chalk.dim('Next steps:'));
|
|
212
|
+
console.log(chalk.dim(' 1. Run `contractual lint` to validate your specs'));
|
|
213
|
+
console.log(chalk.dim(' 2. Make changes to your specs'));
|
|
214
|
+
console.log(chalk.dim(' 3. Run `contractual diff` to see changes'));
|
|
215
|
+
console.log(chalk.dim(' 4. Run `contractual changeset` to record changes'));
|
|
216
|
+
}
|
|
217
|
+
catch (error) {
|
|
218
|
+
spinner.fail('Initialization failed');
|
|
219
|
+
const message = error instanceof Error ? error.message : 'Unknown error';
|
|
220
|
+
console.error(chalk.red(message));
|
|
221
|
+
process.exitCode = 1;
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
/**
|
|
225
|
+
* Handle existing project - initialize uninitialized contracts
|
|
226
|
+
*/
|
|
227
|
+
async function handleExistingProject(cwd, configPath, contractualDir, options) {
|
|
228
|
+
// Read existing config
|
|
229
|
+
const configContent = readFileSync(configPath, 'utf-8');
|
|
230
|
+
const config = parseYaml(configContent);
|
|
231
|
+
if (!config.contracts || config.contracts.length === 0) {
|
|
232
|
+
console.log(chalk.red('Already initialized:') + ' contractual.yaml exists');
|
|
233
|
+
console.log(chalk.dim('Use `contractual status` to see current state'));
|
|
234
|
+
process.exitCode = 1;
|
|
235
|
+
return;
|
|
236
|
+
}
|
|
237
|
+
// Ensure .contractual directory exists
|
|
238
|
+
ensureContractualDir(cwd);
|
|
239
|
+
// Find contracts without snapshots
|
|
240
|
+
const uninitializedContracts = [];
|
|
241
|
+
for (const contract of config.contracts) {
|
|
242
|
+
const snapshotPath = getSnapshotPath(contract.name, contractualDir);
|
|
243
|
+
if (!snapshotPath) {
|
|
244
|
+
uninitializedContracts.push(contract);
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
if (uninitializedContracts.length === 0) {
|
|
248
|
+
console.log(chalk.yellow('Already initialized:') + ' contractual.yaml exists');
|
|
249
|
+
console.log(chalk.dim('All contracts have snapshots.'));
|
|
250
|
+
console.log(chalk.dim('Use `contractual status` to see current state'));
|
|
251
|
+
console.log(chalk.dim('Use `--force` to reinitialize'));
|
|
252
|
+
return;
|
|
253
|
+
}
|
|
254
|
+
// Show uninitialized contracts
|
|
255
|
+
console.log(chalk.yellow(`Found ${uninitializedContracts.length} contract(s) without version history:`));
|
|
256
|
+
for (const contract of uninitializedContracts) {
|
|
257
|
+
console.log(` ${chalk.dim('-')} ${chalk.cyan(contract.name)} ${chalk.dim(`(${contract.type})`)}`);
|
|
258
|
+
}
|
|
259
|
+
console.log();
|
|
260
|
+
// Confirm initialization
|
|
261
|
+
const shouldInitialize = await promptConfirm(`Initialize with version ${DEFAULT_VERSION}?`, true, options);
|
|
262
|
+
if (!shouldInitialize) {
|
|
263
|
+
console.log(chalk.dim('Skipped initialization'));
|
|
264
|
+
return;
|
|
265
|
+
}
|
|
266
|
+
// Initialize uninitialized contracts
|
|
267
|
+
const versionManager = new VersionManager(contractualDir);
|
|
268
|
+
for (const contract of uninitializedContracts) {
|
|
269
|
+
const absolutePath = join(cwd, contract.path);
|
|
270
|
+
if (!existsSync(absolutePath)) {
|
|
271
|
+
console.log(chalk.yellow(` Skipped ${contract.name}: spec file not found at ${contract.path}`));
|
|
272
|
+
continue;
|
|
273
|
+
}
|
|
274
|
+
versionManager.setVersion(contract.name, DEFAULT_VERSION, absolutePath);
|
|
275
|
+
console.log(chalk.green('✓') + ` Initialized ${chalk.cyan(contract.name)} at v${DEFAULT_VERSION}`);
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
/**
|
|
279
|
+
* Get chalk color function for contract type
|
|
280
|
+
*/
|
|
281
|
+
function getTypeColor(type) {
|
|
282
|
+
switch (type) {
|
|
283
|
+
case 'openapi':
|
|
284
|
+
return chalk.green;
|
|
285
|
+
case 'asyncapi':
|
|
286
|
+
return chalk.magenta;
|
|
287
|
+
case 'json-schema':
|
|
288
|
+
return chalk.blue;
|
|
289
|
+
case 'odcs':
|
|
290
|
+
return chalk.yellow;
|
|
291
|
+
default:
|
|
292
|
+
return chalk.white;
|
|
293
|
+
}
|
|
294
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Options for the lint command
|
|
3
|
+
*/
|
|
4
|
+
export interface LintOptions {
|
|
5
|
+
/** Filter to specific contract name */
|
|
6
|
+
contract?: string;
|
|
7
|
+
/** Output format */
|
|
8
|
+
format?: 'text' | 'json';
|
|
9
|
+
/** Exit 1 on warnings */
|
|
10
|
+
failOnWarn?: boolean;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Run linters for all configured contracts
|
|
14
|
+
*/
|
|
15
|
+
export declare function lintCommand(options?: LintOptions): Promise<void>;
|
|
16
|
+
//# sourceMappingURL=lint.command.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"lint.command.d.ts","sourceRoot":"","sources":["../../src/commands/lint.command.ts"],"names":[],"mappings":"AAOA;;GAEG;AACH,MAAM,WAAW,WAAW;IAC1B,uCAAuC;IACvC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,oBAAoB;IACpB,MAAM,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IACzB,yBAAyB;IACzB,UAAU,CAAC,EAAE,OAAO,CAAC;CACtB;AAiCD;;GAEG;AACH,wBAAsB,WAAW,CAAC,OAAO,GAAE,WAAgB,GAAG,OAAO,CAAC,IAAI,CAAC,CA0K1E"}
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import ora from 'ora';
|
|
3
|
+
import { loadConfig } from '../config/index.js';
|
|
4
|
+
import { getLinter as getRegisteredLinter } from '../governance/index.js';
|
|
5
|
+
import { printSuccess, printError, printWarning } from '../utils/output.js';
|
|
6
|
+
/**
|
|
7
|
+
* Get linter for a contract using the governance registry
|
|
8
|
+
*/
|
|
9
|
+
function getLinterForContract(contract) {
|
|
10
|
+
// Check if linting is explicitly disabled
|
|
11
|
+
if (contract.lint === false) {
|
|
12
|
+
return { status: 'disabled' };
|
|
13
|
+
}
|
|
14
|
+
// Use the governance registry to get the linter
|
|
15
|
+
const linter = getRegisteredLinter(contract.type, contract.lint);
|
|
16
|
+
if (linter === null) {
|
|
17
|
+
return { status: 'disabled' };
|
|
18
|
+
}
|
|
19
|
+
if (!linter) {
|
|
20
|
+
return { status: 'not-found', type: contract.type };
|
|
21
|
+
}
|
|
22
|
+
return { status: 'found', linter };
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Run linters for all configured contracts
|
|
26
|
+
*/
|
|
27
|
+
export async function lintCommand(options = {}) {
|
|
28
|
+
const { contract: filterContract, format = 'text', failOnWarn = false } = options;
|
|
29
|
+
try {
|
|
30
|
+
// Load config
|
|
31
|
+
const config = loadConfig();
|
|
32
|
+
// Filter contracts if specified
|
|
33
|
+
let contracts = config.contracts;
|
|
34
|
+
if (filterContract) {
|
|
35
|
+
contracts = contracts.filter((c) => c.name === filterContract);
|
|
36
|
+
if (contracts.length === 0) {
|
|
37
|
+
if (format === 'json') {
|
|
38
|
+
console.log(JSON.stringify({ error: `Contract "${filterContract}" not found` }));
|
|
39
|
+
}
|
|
40
|
+
else {
|
|
41
|
+
printError(`Contract "${filterContract}" not found`);
|
|
42
|
+
}
|
|
43
|
+
process.exitCode = 1;
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
if (contracts.length === 0) {
|
|
48
|
+
if (format === 'json') {
|
|
49
|
+
console.log(JSON.stringify({ results: [], errors: 0, warnings: 0 }));
|
|
50
|
+
}
|
|
51
|
+
else {
|
|
52
|
+
printWarning('No contracts configured');
|
|
53
|
+
}
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
const results = [];
|
|
57
|
+
const spinner = format === 'text' ? ora('Linting contracts...').start() : null;
|
|
58
|
+
for (const contract of contracts) {
|
|
59
|
+
if (spinner) {
|
|
60
|
+
spinner.text = `Linting ${contract.name}...`;
|
|
61
|
+
}
|
|
62
|
+
const linterResult = getLinterForContract(contract);
|
|
63
|
+
// Linting explicitly disabled
|
|
64
|
+
if (linterResult.status === 'disabled') {
|
|
65
|
+
if (format === 'text') {
|
|
66
|
+
spinner?.stopAndPersist({
|
|
67
|
+
symbol: chalk.dim('-'),
|
|
68
|
+
text: `${contract.name}: ${chalk.dim('linting disabled')}`,
|
|
69
|
+
});
|
|
70
|
+
spinner?.start();
|
|
71
|
+
}
|
|
72
|
+
continue;
|
|
73
|
+
}
|
|
74
|
+
// No linter registered for this type
|
|
75
|
+
if (linterResult.status === 'not-found') {
|
|
76
|
+
if (format === 'text') {
|
|
77
|
+
spinner?.stopAndPersist({
|
|
78
|
+
symbol: chalk.yellow('!'),
|
|
79
|
+
text: `${contract.name}: ${chalk.yellow(`no linter available for ${linterResult.type}`)}`,
|
|
80
|
+
});
|
|
81
|
+
spinner?.start();
|
|
82
|
+
}
|
|
83
|
+
continue;
|
|
84
|
+
}
|
|
85
|
+
try {
|
|
86
|
+
const result = await linterResult.linter(contract.absolutePath);
|
|
87
|
+
results.push({
|
|
88
|
+
...result,
|
|
89
|
+
contract: contract.name,
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
catch (error) {
|
|
93
|
+
const message = error instanceof Error ? error.message : 'Linter execution failed';
|
|
94
|
+
results.push({
|
|
95
|
+
contract: contract.name,
|
|
96
|
+
specPath: contract.absolutePath,
|
|
97
|
+
errors: [
|
|
98
|
+
{
|
|
99
|
+
path: '',
|
|
100
|
+
message,
|
|
101
|
+
severity: 'error',
|
|
102
|
+
},
|
|
103
|
+
],
|
|
104
|
+
warnings: [],
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
spinner?.stop();
|
|
109
|
+
// Calculate totals
|
|
110
|
+
const totalErrors = results.reduce((sum, r) => sum + r.errors.length, 0);
|
|
111
|
+
const totalWarnings = results.reduce((sum, r) => sum + r.warnings.length, 0);
|
|
112
|
+
// Output results
|
|
113
|
+
if (format === 'json') {
|
|
114
|
+
console.log(JSON.stringify({
|
|
115
|
+
results,
|
|
116
|
+
errors: totalErrors,
|
|
117
|
+
warnings: totalWarnings,
|
|
118
|
+
}, null, 2));
|
|
119
|
+
}
|
|
120
|
+
else {
|
|
121
|
+
// Text format output
|
|
122
|
+
console.log();
|
|
123
|
+
for (const result of results) {
|
|
124
|
+
const hasErrors = result.errors.length > 0;
|
|
125
|
+
const hasWarnings = result.warnings.length > 0;
|
|
126
|
+
if (!hasErrors && !hasWarnings) {
|
|
127
|
+
printSuccess(`${result.contract}: No issues found`);
|
|
128
|
+
continue;
|
|
129
|
+
}
|
|
130
|
+
// Print contract header
|
|
131
|
+
const errorCount = result.errors.length;
|
|
132
|
+
const warningCount = result.warnings.length;
|
|
133
|
+
const summary = [];
|
|
134
|
+
if (errorCount > 0)
|
|
135
|
+
summary.push(chalk.red(`${errorCount} error(s)`));
|
|
136
|
+
if (warningCount > 0)
|
|
137
|
+
summary.push(chalk.yellow(`${warningCount} warning(s)`));
|
|
138
|
+
console.log(`${chalk.bold(result.contract)}: ${summary.join(', ')}`);
|
|
139
|
+
// Print errors
|
|
140
|
+
for (const error of result.errors) {
|
|
141
|
+
const location = error.path ? chalk.dim(`[${error.path}]`) : '';
|
|
142
|
+
const rule = error.rule ? chalk.dim(`(${error.rule})`) : '';
|
|
143
|
+
console.log(` ${chalk.red('error')} ${location} ${error.message} ${rule}`);
|
|
144
|
+
}
|
|
145
|
+
// Print warnings
|
|
146
|
+
for (const warning of result.warnings) {
|
|
147
|
+
const location = warning.path ? chalk.dim(`[${warning.path}]`) : '';
|
|
148
|
+
const rule = warning.rule ? chalk.dim(`(${warning.rule})`) : '';
|
|
149
|
+
console.log(` ${chalk.yellow('warn')} ${location} ${warning.message} ${rule}`);
|
|
150
|
+
}
|
|
151
|
+
console.log();
|
|
152
|
+
}
|
|
153
|
+
// Summary
|
|
154
|
+
if (results.length > 0) {
|
|
155
|
+
console.log(chalk.dim(`Checked ${results.length} contract(s): ` +
|
|
156
|
+
`${totalErrors} error(s), ${totalWarnings} warning(s)`));
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
// Exit with error code if there were errors (or warnings if --fail-on-warn)
|
|
160
|
+
if (totalErrors > 0 || (failOnWarn && totalWarnings > 0)) {
|
|
161
|
+
process.exitCode = 1;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
catch (error) {
|
|
165
|
+
const message = error instanceof Error ? error.message : 'Unknown error';
|
|
166
|
+
if (options.format === 'json') {
|
|
167
|
+
console.log(JSON.stringify({ error: message }));
|
|
168
|
+
}
|
|
169
|
+
else {
|
|
170
|
+
printError(message);
|
|
171
|
+
}
|
|
172
|
+
process.exitCode = 1;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Enter pre-release mode
|
|
3
|
+
* @param tag - The pre-release tag (e.g., "alpha", "beta", "rc")
|
|
4
|
+
*/
|
|
5
|
+
export declare function preEnterCommand(tag: string): Promise<void>;
|
|
6
|
+
/**
|
|
7
|
+
* Exit pre-release mode
|
|
8
|
+
*/
|
|
9
|
+
export declare function preExitCommand(): Promise<void>;
|
|
10
|
+
/**
|
|
11
|
+
* Show pre-release status
|
|
12
|
+
*/
|
|
13
|
+
export declare function preStatusCommand(): Promise<void>;
|
|
14
|
+
//# sourceMappingURL=pre.command.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pre.command.d.ts","sourceRoot":"","sources":["../../src/commands/pre.command.ts"],"names":[],"mappings":"AAIA;;;GAGG;AACH,wBAAsB,eAAe,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAsChE;AAED;;GAEG;AACH,wBAAsB,cAAc,IAAI,OAAO,CAAC,IAAI,CAAC,CA+DpD;AAED;;GAEG;AACH,wBAAsB,gBAAgB,IAAI,OAAO,CAAC,IAAI,CAAC,CAoDtD"}
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import { VersionManager, PreReleaseManager } from '@contractual/changesets';
|
|
3
|
+
import { findContractualDir } from '../utils/files.js';
|
|
4
|
+
/**
|
|
5
|
+
* Enter pre-release mode
|
|
6
|
+
* @param tag - The pre-release tag (e.g., "alpha", "beta", "rc")
|
|
7
|
+
*/
|
|
8
|
+
export async function preEnterCommand(tag) {
|
|
9
|
+
const cwd = process.cwd();
|
|
10
|
+
const contractualDir = findContractualDir(cwd);
|
|
11
|
+
if (!contractualDir) {
|
|
12
|
+
console.error(chalk.red('No .contractual directory found. Run `contractual init` first.'));
|
|
13
|
+
process.exitCode = 1;
|
|
14
|
+
return;
|
|
15
|
+
}
|
|
16
|
+
try {
|
|
17
|
+
const versionManager = new VersionManager(contractualDir);
|
|
18
|
+
const preManager = new PreReleaseManager(contractualDir);
|
|
19
|
+
if (preManager.isActive()) {
|
|
20
|
+
const state = preManager.getState();
|
|
21
|
+
console.log(chalk.yellow('Already in pre-release mode:') + ` ${state?.tag}`);
|
|
22
|
+
console.log(chalk.dim('Run `contractual pre exit` to leave pre-release mode first.'));
|
|
23
|
+
process.exitCode = 1;
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
preManager.enter(tag, versionManager);
|
|
27
|
+
console.log(chalk.green('✓') + ` Entered pre-release mode: ${chalk.cyan(tag)}`);
|
|
28
|
+
console.log();
|
|
29
|
+
console.log(chalk.bold('Created:'));
|
|
30
|
+
console.log(` ${chalk.green('+')} .contractual/pre.json`);
|
|
31
|
+
console.log();
|
|
32
|
+
console.log(chalk.dim(`Next versions will use ${tag} identifier (e.g., 2.0.0-${tag}.0)`));
|
|
33
|
+
console.log(chalk.dim('Run `contractual version` to apply changesets with pre-release versions.'));
|
|
34
|
+
}
|
|
35
|
+
catch (error) {
|
|
36
|
+
const message = error instanceof Error ? error.message : 'Unknown error';
|
|
37
|
+
console.error(chalk.red('Failed to enter pre-release mode:'), message);
|
|
38
|
+
process.exitCode = 1;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Exit pre-release mode
|
|
43
|
+
*/
|
|
44
|
+
export async function preExitCommand() {
|
|
45
|
+
const cwd = process.cwd();
|
|
46
|
+
const contractualDir = findContractualDir(cwd);
|
|
47
|
+
if (!contractualDir) {
|
|
48
|
+
console.error(chalk.red('No .contractual directory found. Run `contractual init` first.'));
|
|
49
|
+
process.exitCode = 1;
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
try {
|
|
53
|
+
const versionManager = new VersionManager(contractualDir);
|
|
54
|
+
const preManager = new PreReleaseManager(contractualDir);
|
|
55
|
+
if (!preManager.isActive()) {
|
|
56
|
+
console.log(chalk.yellow('Not in pre-release mode.'));
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
const state = preManager.getState();
|
|
60
|
+
const currentVersions = versionManager.getAllVersions();
|
|
61
|
+
// Show what will change
|
|
62
|
+
console.log(chalk.bold('Exiting pre-release mode'));
|
|
63
|
+
console.log();
|
|
64
|
+
const hasPreReleaseVersions = Object.entries(currentVersions).some(([_, version]) => version.includes('-'));
|
|
65
|
+
if (hasPreReleaseVersions) {
|
|
66
|
+
console.log(chalk.dim('Current pre-release versions:'));
|
|
67
|
+
for (const [contract, version] of Object.entries(currentVersions)) {
|
|
68
|
+
if (version.includes('-')) {
|
|
69
|
+
// Extract base version
|
|
70
|
+
const baseVersion = version.split('-')[0];
|
|
71
|
+
console.log(` ${chalk.cyan(contract)}: ${chalk.gray(version)} → ${chalk.green(baseVersion)}`);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
console.log();
|
|
75
|
+
console.log(chalk.dim('Run `contractual version` after exiting to finalize versions.'));
|
|
76
|
+
}
|
|
77
|
+
preManager.exit();
|
|
78
|
+
console.log();
|
|
79
|
+
console.log(chalk.green('✓') + ' Exited pre-release mode');
|
|
80
|
+
console.log();
|
|
81
|
+
console.log(chalk.bold('Removed:'));
|
|
82
|
+
console.log(` ${chalk.red('-')} .contractual/pre.json`);
|
|
83
|
+
if (state) {
|
|
84
|
+
console.log();
|
|
85
|
+
console.log(chalk.dim(`Pre-release tag was: ${state.tag}`));
|
|
86
|
+
console.log(chalk.dim(`Entered at: ${new Date(state.enteredAt).toLocaleString()}`));
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
catch (error) {
|
|
90
|
+
const message = error instanceof Error ? error.message : 'Unknown error';
|
|
91
|
+
console.error(chalk.red('Failed to exit pre-release mode:'), message);
|
|
92
|
+
process.exitCode = 1;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Show pre-release status
|
|
97
|
+
*/
|
|
98
|
+
export async function preStatusCommand() {
|
|
99
|
+
const cwd = process.cwd();
|
|
100
|
+
const contractualDir = findContractualDir(cwd);
|
|
101
|
+
if (!contractualDir) {
|
|
102
|
+
console.error(chalk.red('No .contractual directory found. Run `contractual init` first.'));
|
|
103
|
+
process.exitCode = 1;
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
const preManager = new PreReleaseManager(contractualDir);
|
|
107
|
+
if (!preManager.isActive()) {
|
|
108
|
+
console.log(chalk.dim('Not in pre-release mode.'));
|
|
109
|
+
console.log(chalk.dim('Run `contractual pre enter <tag>` to enter pre-release mode.'));
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
const state = preManager.getState();
|
|
113
|
+
if (!state) {
|
|
114
|
+
console.log(chalk.yellow('Pre-release state file is corrupted.'));
|
|
115
|
+
console.log(chalk.dim('Run `contractual pre exit` to reset.'));
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
const versionManager = new VersionManager(contractualDir);
|
|
119
|
+
const currentVersions = versionManager.getAllVersions();
|
|
120
|
+
console.log(chalk.bold('Pre-release Status'));
|
|
121
|
+
console.log();
|
|
122
|
+
console.log(` ${chalk.dim('Tag:')} ${chalk.cyan(state.tag)}`);
|
|
123
|
+
console.log(` ${chalk.dim('Since:')} ${new Date(state.enteredAt).toLocaleString()}`);
|
|
124
|
+
// Show version changes since entering pre-release
|
|
125
|
+
const changedContracts = Object.entries(currentVersions).filter(([name, version]) => {
|
|
126
|
+
const initial = state.initialVersions[name];
|
|
127
|
+
return initial && initial !== version;
|
|
128
|
+
});
|
|
129
|
+
if (changedContracts.length > 0) {
|
|
130
|
+
console.log();
|
|
131
|
+
console.log(chalk.bold('Version changes since entering pre-release:'));
|
|
132
|
+
for (const [name, version] of changedContracts) {
|
|
133
|
+
const initial = state.initialVersions[name];
|
|
134
|
+
console.log(` ${chalk.cyan(name)}: ${chalk.gray(initial)} → ${chalk.green(version)}`);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
console.log();
|
|
138
|
+
console.log(chalk.dim('Commands:'));
|
|
139
|
+
console.log(chalk.dim(' contractual version Apply changesets with pre-release versions'));
|
|
140
|
+
console.log(chalk.dim(' contractual pre exit Exit pre-release mode'));
|
|
141
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"status.command.d.ts","sourceRoot":"","sources":["../../src/commands/status.command.ts"],"names":[],"mappings":"AA0BA;;GAEG;AACH,wBAAsB,aAAa,IAAI,OAAO,CAAC,IAAI,CAAC,CAkHnD"}
|