@thejoseki/clawform 2.3.1 → 2.3.2
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/README.md +141 -120
- package/bin/clawform.js +161 -151
- package/package.json +65 -65
- package/src/aws/catalog.js +116 -116
- package/src/aws/changeset.js +231 -231
- package/src/aws/drift.js +59 -59
- package/src/aws/exports.js +213 -213
- package/src/aws/inventory.js +156 -156
- package/src/commands/activate.js +137 -105
- package/src/commands/ci-init.js +393 -212
- package/src/commands/cost-report.js +163 -163
- package/src/commands/deactivate.js +9 -2
- package/src/commands/deploy.js +413 -413
- package/src/commands/diff.js +39 -39
- package/src/commands/drift.js +35 -35
- package/src/commands/init-plugin.js +6 -6
- package/src/commands/init.js +360 -360
- package/src/commands/rollback.js +126 -126
- package/src/commands/status.js +147 -147
- package/src/commands/sync.js +50 -50
- package/src/commands/update.js +24 -0
- package/src/commands/validate.js +349 -349
- package/src/hooks/block-dangerous.js +62 -62
- package/src/hooks/cfn-lint-after-edit-eval.js +30 -30
- package/src/hooks/cfn-lint-after-edit.js +81 -81
- package/src/hooks/check-catalog-fresh-eval.js +63 -63
- package/src/hooks/check-catalog-fresh.js +33 -33
- package/src/hooks/session-context-eval.js +81 -81
- package/src/hooks/session-context.js +41 -41
- package/src/hooks/subagent-context-eval.js +44 -44
- package/src/hooks/subagent-context.js +48 -48
- package/src/lib/audit-synthesis.js +24 -24
- package/src/lib/aws-client.js +15 -15
- package/src/lib/cfn-yaml.js +92 -92
- package/src/lib/config.js +76 -76
- package/src/lib/constants.js +85 -85
- package/src/lib/defaults.js +16 -16
- package/src/lib/install-workflow.js +28 -28
- package/src/lib/kit-source.js +43 -5
- package/src/lib/license-client.js +84 -84
- package/src/lib/license-config.js +162 -162
- package/src/lib/license-store.js +43 -8
- package/src/lib/license.js +163 -150
- package/src/lib/lint-overrides.js +226 -226
- package/src/lib/logger.js +16 -16
- package/src/lib/project-config.js +145 -145
- package/src/lib/providers/polar.js +215 -215
- package/src/lib/session-state.js +91 -91
package/src/commands/diff.js
CHANGED
|
@@ -1,39 +1,39 @@
|
|
|
1
|
-
import { readFile } from 'node:fs/promises';
|
|
2
|
-
import { existsSync } from 'node:fs';
|
|
3
|
-
import { GetTemplateCommand } from '@aws-sdk/client-cloudformation';
|
|
4
|
-
import { createPatch } from 'diff';
|
|
5
|
-
import chalk from 'chalk';
|
|
6
|
-
import { logger } from '../lib/logger.js';
|
|
7
|
-
import { cfnClient } from '../lib/aws-client.js';
|
|
8
|
-
|
|
9
|
-
export default async function diff({ stack, file }) {
|
|
10
|
-
const localPath = file ?? `cfn/${stack}.yaml`;
|
|
11
|
-
logger.step(`clawform diff ${stack} ↔ ${localPath}`);
|
|
12
|
-
|
|
13
|
-
if (!existsSync(localPath)) {
|
|
14
|
-
throw new Error(`Local template not found: ${localPath}. Pass --file <path> to override.`);
|
|
15
|
-
}
|
|
16
|
-
// Normalize line endings before comparison. CFN's GetTemplate returns LF, but
|
|
17
|
-
// fs.readFile preserves whatever bytes are on disk — on Windows checkouts (git's
|
|
18
|
-
// default core.autocrlf=true) every line of an identical template differs by a
|
|
19
|
-
// trailing \r, producing a wall-of-red diff that destroys the command's value.
|
|
20
|
-
const local = (await readFile(localPath, 'utf8')).replace(/\r\n/g, '\n');
|
|
21
|
-
|
|
22
|
-
const client = cfnClient();
|
|
23
|
-
const out = await client.send(new GetTemplateCommand({ StackName: stack, TemplateStage: 'Original' }));
|
|
24
|
-
const live = (out.TemplateBody ?? '').replace(/\r\n/g, '\n');
|
|
25
|
-
|
|
26
|
-
if (live === local) {
|
|
27
|
-
logger.ok('No diff. Local template matches live stack.');
|
|
28
|
-
return;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
const patch = createPatch(stack, live, local, 'live', 'local', { context: 3 });
|
|
32
|
-
for (const line of patch.split('\n')) {
|
|
33
|
-
if (line.startsWith('+++ ') || line.startsWith('--- ')) console.log(chalk.bold(line));
|
|
34
|
-
else if (line.startsWith('+')) console.log(chalk.green(line));
|
|
35
|
-
else if (line.startsWith('-')) console.log(chalk.red(line));
|
|
36
|
-
else if (line.startsWith('@@')) console.log(chalk.cyan(line));
|
|
37
|
-
else console.log(line);
|
|
38
|
-
}
|
|
39
|
-
}
|
|
1
|
+
import { readFile } from 'node:fs/promises';
|
|
2
|
+
import { existsSync } from 'node:fs';
|
|
3
|
+
import { GetTemplateCommand } from '@aws-sdk/client-cloudformation';
|
|
4
|
+
import { createPatch } from 'diff';
|
|
5
|
+
import chalk from 'chalk';
|
|
6
|
+
import { logger } from '../lib/logger.js';
|
|
7
|
+
import { cfnClient } from '../lib/aws-client.js';
|
|
8
|
+
|
|
9
|
+
export default async function diff({ stack, file }) {
|
|
10
|
+
const localPath = file ?? `cfn/${stack}.yaml`;
|
|
11
|
+
logger.step(`clawform diff ${stack} ↔ ${localPath}`);
|
|
12
|
+
|
|
13
|
+
if (!existsSync(localPath)) {
|
|
14
|
+
throw new Error(`Local template not found: ${localPath}. Pass --file <path> to override.`);
|
|
15
|
+
}
|
|
16
|
+
// Normalize line endings before comparison. CFN's GetTemplate returns LF, but
|
|
17
|
+
// fs.readFile preserves whatever bytes are on disk — on Windows checkouts (git's
|
|
18
|
+
// default core.autocrlf=true) every line of an identical template differs by a
|
|
19
|
+
// trailing \r, producing a wall-of-red diff that destroys the command's value.
|
|
20
|
+
const local = (await readFile(localPath, 'utf8')).replace(/\r\n/g, '\n');
|
|
21
|
+
|
|
22
|
+
const client = cfnClient();
|
|
23
|
+
const out = await client.send(new GetTemplateCommand({ StackName: stack, TemplateStage: 'Original' }));
|
|
24
|
+
const live = (out.TemplateBody ?? '').replace(/\r\n/g, '\n');
|
|
25
|
+
|
|
26
|
+
if (live === local) {
|
|
27
|
+
logger.ok('No diff. Local template matches live stack.');
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const patch = createPatch(stack, live, local, 'live', 'local', { context: 3 });
|
|
32
|
+
for (const line of patch.split('\n')) {
|
|
33
|
+
if (line.startsWith('+++ ') || line.startsWith('--- ')) console.log(chalk.bold(line));
|
|
34
|
+
else if (line.startsWith('+')) console.log(chalk.green(line));
|
|
35
|
+
else if (line.startsWith('-')) console.log(chalk.red(line));
|
|
36
|
+
else if (line.startsWith('@@')) console.log(chalk.cyan(line));
|
|
37
|
+
else console.log(line);
|
|
38
|
+
}
|
|
39
|
+
}
|
package/src/commands/drift.js
CHANGED
|
@@ -1,35 +1,35 @@
|
|
|
1
|
-
import chalk from 'chalk';
|
|
2
|
-
import { logger } from '../lib/logger.js';
|
|
3
|
-
import { cfnClient } from '../lib/aws-client.js';
|
|
4
|
-
import { detectAndWait, listResourceDrifts } from '../aws/drift.js';
|
|
5
|
-
|
|
6
|
-
export default async function drift({ stack }) {
|
|
7
|
-
logger.step(`clawform drift ${stack}`);
|
|
8
|
-
const client = cfnClient();
|
|
9
|
-
|
|
10
|
-
logger.info('Detecting drift (this can take a few minutes)…');
|
|
11
|
-
const summary = await detectAndWait(client, { stackName: stack });
|
|
12
|
-
logger.info(`StackDriftStatus: ${summary.stackDriftStatus} · drifted resources: ${summary.driftedResourceCount}`);
|
|
13
|
-
|
|
14
|
-
if (summary.stackDriftStatus === 'IN_SYNC') {
|
|
15
|
-
logger.ok('No drift.');
|
|
16
|
-
return;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
const drifts = await listResourceDrifts(client, stack);
|
|
20
|
-
for (const d of drifts) {
|
|
21
|
-
logger.info('');
|
|
22
|
-
logger.info(`${chalk.yellow(d.driftStatus)} ${d.logicalId} (${d.resourceType})`);
|
|
23
|
-
for (const p of d.propertyDifferences) {
|
|
24
|
-
const status = p.DifferenceType ?? '?';
|
|
25
|
-
logger.info(` ${chalk.dim('@')}${p.PropertyPath ?? '/'} ${status}`);
|
|
26
|
-
if (p.ExpectedValue !== undefined) logger.info(` expected: ${truncate(p.ExpectedValue)}`);
|
|
27
|
-
if (p.ActualValue !== undefined) logger.info(` actual: ${truncate(p.ActualValue)}`);
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
function truncate(v, max = 120) {
|
|
33
|
-
const s = typeof v === 'string' ? v : JSON.stringify(v);
|
|
34
|
-
return s.length > max ? s.slice(0, max - 1) + '…' : s;
|
|
35
|
-
}
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import { logger } from '../lib/logger.js';
|
|
3
|
+
import { cfnClient } from '../lib/aws-client.js';
|
|
4
|
+
import { detectAndWait, listResourceDrifts } from '../aws/drift.js';
|
|
5
|
+
|
|
6
|
+
export default async function drift({ stack }) {
|
|
7
|
+
logger.step(`clawform drift ${stack}`);
|
|
8
|
+
const client = cfnClient();
|
|
9
|
+
|
|
10
|
+
logger.info('Detecting drift (this can take a few minutes)…');
|
|
11
|
+
const summary = await detectAndWait(client, { stackName: stack });
|
|
12
|
+
logger.info(`StackDriftStatus: ${summary.stackDriftStatus} · drifted resources: ${summary.driftedResourceCount}`);
|
|
13
|
+
|
|
14
|
+
if (summary.stackDriftStatus === 'IN_SYNC') {
|
|
15
|
+
logger.ok('No drift.');
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const drifts = await listResourceDrifts(client, stack);
|
|
20
|
+
for (const d of drifts) {
|
|
21
|
+
logger.info('');
|
|
22
|
+
logger.info(`${chalk.yellow(d.driftStatus)} ${d.logicalId} (${d.resourceType})`);
|
|
23
|
+
for (const p of d.propertyDifferences) {
|
|
24
|
+
const status = p.DifferenceType ?? '?';
|
|
25
|
+
logger.info(` ${chalk.dim('@')}${p.PropertyPath ?? '/'} ${status}`);
|
|
26
|
+
if (p.ExpectedValue !== undefined) logger.info(` expected: ${truncate(p.ExpectedValue)}`);
|
|
27
|
+
if (p.ActualValue !== undefined) logger.info(` actual: ${truncate(p.ActualValue)}`);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function truncate(v, max = 120) {
|
|
33
|
+
const s = typeof v === 'string' ? v : JSON.stringify(v);
|
|
34
|
+
return s.length > max ? s.slice(0, max - 1) + '…' : s;
|
|
35
|
+
}
|
|
@@ -115,7 +115,7 @@ export function assemblePlugin({ packageRoot, destDir, entries, licenseRecord, n
|
|
|
115
115
|
return manifest;
|
|
116
116
|
}
|
|
117
117
|
|
|
118
|
-
export default async function initPlugin({ scope = 'global' } = {}, {
|
|
118
|
+
export default async function initPlugin({ scope = 'global', refresh = false } = {}, {
|
|
119
119
|
packageRoot = PACKAGE_ROOT,
|
|
120
120
|
homedir = osHomedir,
|
|
121
121
|
client = resolveClient(),
|
|
@@ -137,16 +137,16 @@ export default async function initPlugin({ scope = 'global' } = {}, {
|
|
|
137
137
|
licenseKey: readLicense()?.key,
|
|
138
138
|
serviceUrl: KIT_SERVICE_URL,
|
|
139
139
|
cacheDir: vendorHome,
|
|
140
|
+
refresh,
|
|
140
141
|
});
|
|
141
142
|
|
|
142
143
|
assemblePlugin({ packageRoot, destDir: pluginDir, entries, licenseRecord: readLicense() });
|
|
143
144
|
log(`Plugin materialized at ${pluginDir}`);
|
|
144
145
|
|
|
145
|
-
// Register with Claude Code.
|
|
146
|
-
//
|
|
147
|
-
//
|
|
148
|
-
|
|
149
|
-
const scopeFlag = scope === 'project' ? ['--project'] : [];
|
|
146
|
+
// Register with Claude Code. Both subcommands take `--scope user|project|local`
|
|
147
|
+
// (default user); clawform "global" maps to user (all projects), "project" to
|
|
148
|
+
// project (./.claude). The runner is injected so this stays testable.
|
|
149
|
+
const scopeFlag = ['--scope', scope === 'project' ? 'project' : 'user'];
|
|
150
150
|
const add = await runClaude(['plugin', 'marketplace', 'add', pluginDir, ...scopeFlag]);
|
|
151
151
|
if (add.code !== 0) throw new Error(`claude plugin marketplace add failed: ${add.stderr ?? `exit ${add.code}`}`);
|
|
152
152
|
const install = await runClaude(['plugin', 'install', 'clawform@clawform', ...scopeFlag]);
|