@nlaprell/shipit 1.0.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/.cursor/commands/create_intent_from_issue.md +28 -0
- package/.cursor/commands/create_pr.md +28 -0
- package/.cursor/commands/dashboard.md +39 -0
- package/.cursor/commands/deploy.md +152 -0
- package/.cursor/commands/drift_check.md +36 -0
- package/.cursor/commands/fix.md +39 -0
- package/.cursor/commands/generate_release_plan.md +31 -0
- package/.cursor/commands/generate_roadmap.md +38 -0
- package/.cursor/commands/help.md +37 -0
- package/.cursor/commands/init_project.md +26 -0
- package/.cursor/commands/kill.md +72 -0
- package/.cursor/commands/new_intent.md +68 -0
- package/.cursor/commands/pr.md +77 -0
- package/.cursor/commands/revert-plan.md +58 -0
- package/.cursor/commands/risk.md +64 -0
- package/.cursor/commands/rollback.md +43 -0
- package/.cursor/commands/scope_project.md +53 -0
- package/.cursor/commands/ship.md +345 -0
- package/.cursor/commands/status.md +71 -0
- package/.cursor/commands/suggest.md +44 -0
- package/.cursor/commands/test_shipit.md +197 -0
- package/.cursor/commands/verify.md +50 -0
- package/.cursor/rules/architect.mdc +84 -0
- package/.cursor/rules/assumption-extractor.mdc +95 -0
- package/.cursor/rules/docs.mdc +66 -0
- package/.cursor/rules/implementer.mdc +112 -0
- package/.cursor/rules/pm.mdc +136 -0
- package/.cursor/rules/qa.mdc +97 -0
- package/.cursor/rules/security.mdc +90 -0
- package/.cursor/rules/steward.mdc +99 -0
- package/.cursor/rules/test-runner.mdc +196 -0
- package/AGENTS.md +121 -0
- package/README.md +264 -0
- package/_system/architecture/CANON.md +159 -0
- package/_system/architecture/invariants.yml +87 -0
- package/_system/architecture/project-schema.json +98 -0
- package/_system/architecture/workflow-state-layout.md +68 -0
- package/_system/artifacts/SYSTEM_STATE.md +43 -0
- package/_system/artifacts/confidence-calibration.json +16 -0
- package/_system/artifacts/dependencies.md +46 -0
- package/_system/artifacts/framework-files-manifest.json +179 -0
- package/_system/artifacts/usage.json +1 -0
- package/_system/behaviors/DO_RELEASE.md +371 -0
- package/_system/behaviors/DO_RELEASE_AI.md +329 -0
- package/_system/behaviors/PREPARE_RELEASE.md +373 -0
- package/_system/behaviors/PREPARE_RELEASE_AI.md +234 -0
- package/_system/behaviors/WORK_ROOT_PLATFORM_ISSUES.md +140 -0
- package/_system/behaviors/WORK_TEST_PLAN_ISSUES.md +380 -0
- package/_system/do-not-repeat/abandoned-designs.md +18 -0
- package/_system/do-not-repeat/bad-patterns.md +19 -0
- package/_system/do-not-repeat/failed-experiments.md +18 -0
- package/_system/do-not-repeat/rejected-libraries.md +19 -0
- package/_system/drift/baselines.md +49 -0
- package/_system/drift/metrics.md +33 -0
- package/_system/golden-data/.gitkeep +0 -0
- package/_system/golden-data/README.md +47 -0
- package/_system/reports/mutation/mutation.html +492 -0
- package/_system/security/audit-allowlist.json +4 -0
- package/bin/create-shipit-app +29 -0
- package/bin/shipit +183 -0
- package/cli/src/commands/check.js +82 -0
- package/cli/src/commands/create.js +195 -0
- package/cli/src/commands/init.js +267 -0
- package/cli/src/commands/upgrade.js +196 -0
- package/cli/src/utils/config.js +27 -0
- package/cli/src/utils/file-copy.js +144 -0
- package/cli/src/utils/gitignore-merge.js +44 -0
- package/cli/src/utils/manifest.js +105 -0
- package/cli/src/utils/package-json-merge.js +163 -0
- package/cli/src/utils/project-json-merge.js +57 -0
- package/cli/src/utils/prompts.js +30 -0
- package/cli/src/utils/stack-detection.js +56 -0
- package/cli/src/utils/stack-files.js +364 -0
- package/cli/src/utils/upgrade-backup.js +159 -0
- package/cli/src/utils/version.js +64 -0
- package/dashboard-app/README.md +73 -0
- package/dashboard-app/eslint.config.js +23 -0
- package/dashboard-app/index.html +13 -0
- package/dashboard-app/package.json +30 -0
- package/dashboard-app/pnpm-lock.yaml +2721 -0
- package/dashboard-app/public/dashboard.json +66 -0
- package/dashboard-app/public/vite.svg +1 -0
- package/dashboard-app/src/App.css +141 -0
- package/dashboard-app/src/App.tsx +155 -0
- package/dashboard-app/src/assets/react.svg +1 -0
- package/dashboard-app/src/index.css +68 -0
- package/dashboard-app/src/main.tsx +10 -0
- package/dashboard-app/tsconfig.app.json +28 -0
- package/dashboard-app/tsconfig.json +4 -0
- package/dashboard-app/tsconfig.node.json +26 -0
- package/dashboard-app/vite.config.ts +7 -0
- package/package.json +116 -0
- package/scripts/README.md +70 -0
- package/scripts/audit-check.sh +125 -0
- package/scripts/calibration-report.sh +198 -0
- package/scripts/check-readiness.sh +155 -0
- package/scripts/collect-metrics.sh +116 -0
- package/scripts/command-manifest.yml +131 -0
- package/scripts/create-test-plan-issue.sh +110 -0
- package/scripts/dashboard-start.sh +16 -0
- package/scripts/deploy.sh +170 -0
- package/scripts/drift-check.sh +93 -0
- package/scripts/execute-rollback.sh +177 -0
- package/scripts/export-dashboard-json.js +208 -0
- package/scripts/fix-intents.sh +239 -0
- package/scripts/generate-dashboard.sh +136 -0
- package/scripts/generate-docs.sh +279 -0
- package/scripts/generate-project-context.sh +142 -0
- package/scripts/generate-release-plan.sh +443 -0
- package/scripts/generate-roadmap.sh +189 -0
- package/scripts/generate-system-state.sh +95 -0
- package/scripts/gh/create-intent-from-issue.sh +82 -0
- package/scripts/gh/create-issue-from-intent.sh +59 -0
- package/scripts/gh/create-pr.sh +41 -0
- package/scripts/gh/link-issue.sh +44 -0
- package/scripts/gh/on-ship-update-issue.sh +42 -0
- package/scripts/headless/README.md +8 -0
- package/scripts/headless/call-llm.js +109 -0
- package/scripts/headless/run-phase.sh +99 -0
- package/scripts/help.sh +271 -0
- package/scripts/init-project.sh +976 -0
- package/scripts/kill-intent.sh +125 -0
- package/scripts/lib/common.sh +29 -0
- package/scripts/lib/intent.sh +61 -0
- package/scripts/lib/progress.sh +57 -0
- package/scripts/lib/suggest-next.sh +131 -0
- package/scripts/lib/validate-intents.sh +240 -0
- package/scripts/lib/verify-outputs.sh +55 -0
- package/scripts/lib/workflow_state.sh +201 -0
- package/scripts/new-intent.sh +271 -0
- package/scripts/publish-npm.sh +28 -0
- package/scripts/scope-project.sh +380 -0
- package/scripts/setup-worktrees.sh +125 -0
- package/scripts/status.sh +278 -0
- package/scripts/suggest.sh +173 -0
- package/scripts/test-headless.sh +47 -0
- package/scripts/test-shipit.sh +52 -0
- package/scripts/test-workflow-state.sh +49 -0
- package/scripts/usage-report.sh +47 -0
- package/scripts/usage.sh +58 -0
- package/scripts/validate-cursor.sh +151 -0
- package/scripts/validate-project.sh +71 -0
- package/scripts/validate-vscode.sh +146 -0
- package/scripts/verify.sh +153 -0
- package/scripts/workflow-orchestrator.sh +97 -0
- package/scripts/workflow-templates/01_analysis.md.tpl +25 -0
- package/scripts/workflow-templates/02_plan.md.tpl +30 -0
- package/scripts/workflow-templates/03_implementation.md.tpl +25 -0
- package/scripts/workflow-templates/04_verification.md.tpl +29 -0
- package/scripts/workflow-templates/05_release_notes.md.tpl +16 -0
- package/scripts/workflow-templates/05_verification_legacy.md.tpl +6 -0
- package/scripts/workflow-templates/active.md.tpl +18 -0
- package/scripts/workflow-templates/phases.yml +39 -0
- package/stryker.conf.json +8 -0
- package/work/intent/templates/api-endpoint.md +124 -0
- package/work/intent/templates/bugfix.md +116 -0
- package/work/intent/templates/frontend-feature.md +115 -0
- package/work/intent/templates/generic.md +122 -0
- package/work/intent/templates/infra-change.md +121 -0
- package/work/intent/templates/refactor.md +116 -0
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Manifest utilities - Read and parse framework-files-manifest.json
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { readFileSync, existsSync } from 'fs';
|
|
6
|
+
import { join, dirname } from 'path';
|
|
7
|
+
import { fileURLToPath } from 'url';
|
|
8
|
+
|
|
9
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
10
|
+
const __dirname = dirname(__filename);
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Get CLI package root directory
|
|
14
|
+
* @returns {string} Path to CLI package root
|
|
15
|
+
*/
|
|
16
|
+
function getCliPackageRoot() {
|
|
17
|
+
// When installed: node_modules/shipit/
|
|
18
|
+
// When local dev: framework root (where bin/ and cli/ are)
|
|
19
|
+
|
|
20
|
+
// Try to find node_modules/shipit from current working directory
|
|
21
|
+
let currentPath = process.cwd();
|
|
22
|
+
for (let i = 0; i < 10; i++) { // Limit depth
|
|
23
|
+
const nodeModulesPath = join(currentPath, 'node_modules', 'shipit');
|
|
24
|
+
if (existsSync(nodeModulesPath)) {
|
|
25
|
+
return nodeModulesPath;
|
|
26
|
+
}
|
|
27
|
+
const parent = dirname(currentPath);
|
|
28
|
+
if (parent === currentPath) break; // Reached root
|
|
29
|
+
currentPath = parent;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// Try relative to CLI source (local dev)
|
|
33
|
+
// cli/src/utils/manifest.js -> framework root
|
|
34
|
+
const localDevPath = join(__dirname, '..', '..', '..');
|
|
35
|
+
if (existsSync(join(localDevPath, '_system', 'artifacts', 'framework-files-manifest.json'))) {
|
|
36
|
+
return localDevPath;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// Try relative to bin/ (when installed globally)
|
|
40
|
+
// bin/shipit -> framework root
|
|
41
|
+
const binPath = join(__dirname, '..', '..');
|
|
42
|
+
if (existsSync(join(binPath, '_system', 'artifacts', 'framework-files-manifest.json'))) {
|
|
43
|
+
return binPath;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return null;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Locate framework-files-manifest.json
|
|
51
|
+
* @returns {string} Path to manifest file
|
|
52
|
+
*/
|
|
53
|
+
export function locateManifest() {
|
|
54
|
+
const packageRoot = getCliPackageRoot();
|
|
55
|
+
if (!packageRoot) {
|
|
56
|
+
throw new Error('ShipIt framework files not found. Reinstall: npm install -g @nlaprell/shipit');
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const manifestPath = join(packageRoot, '_system', 'artifacts', 'framework-files-manifest.json');
|
|
60
|
+
if (existsSync(manifestPath)) {
|
|
61
|
+
return manifestPath;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
throw new Error('ShipIt framework files not found. Reinstall: npm install -g @nlaprell/shipit');
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Read and parse framework-files-manifest.json
|
|
69
|
+
* @returns {object} Parsed manifest
|
|
70
|
+
*/
|
|
71
|
+
export function readManifest() {
|
|
72
|
+
const manifestPath = locateManifest();
|
|
73
|
+
|
|
74
|
+
try {
|
|
75
|
+
const content = readFileSync(manifestPath, 'utf-8');
|
|
76
|
+
const manifest = JSON.parse(content);
|
|
77
|
+
|
|
78
|
+
// Validate structure
|
|
79
|
+
if (!manifest.frameworkOwned || !manifest.userOwned || !manifest.stackSpecific || !manifest.neverCopied) {
|
|
80
|
+
throw new Error('Invalid manifest structure: missing required sections');
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
return manifest;
|
|
84
|
+
} catch (error) {
|
|
85
|
+
if (error.code === 'ENOENT') {
|
|
86
|
+
throw new Error('ShipIt framework files not found. Reinstall: npm install -g @nlaprell/shipit');
|
|
87
|
+
}
|
|
88
|
+
if (error instanceof SyntaxError) {
|
|
89
|
+
throw new Error(`Invalid manifest JSON: ${error.message}`);
|
|
90
|
+
}
|
|
91
|
+
throw error;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Get framework root directory (where manifest is located)
|
|
97
|
+
* @returns {string} Framework root directory
|
|
98
|
+
*/
|
|
99
|
+
export function getFrameworkRoot() {
|
|
100
|
+
const packageRoot = getCliPackageRoot();
|
|
101
|
+
if (!packageRoot) {
|
|
102
|
+
throw new Error('ShipIt framework files not found. Reinstall: npm install -g @nlaprell/shipit');
|
|
103
|
+
}
|
|
104
|
+
return packageRoot;
|
|
105
|
+
}
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Package.json merge utilities
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { readFileSync, writeFileSync, existsSync } from 'fs';
|
|
6
|
+
import { join } from 'path';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Merge ShipIt scripts into existing package.json
|
|
10
|
+
* @param {string} existingPath - Path to existing package.json
|
|
11
|
+
* @param {object} shipitScripts - ShipIt scripts to merge
|
|
12
|
+
* @param {object} shipitDevDeps - ShipIt devDependencies to merge
|
|
13
|
+
* @param {object} options - Options (dryRun, verbose)
|
|
14
|
+
* @returns {object} Merge result
|
|
15
|
+
*/
|
|
16
|
+
export function mergePackageJson(existingPath, shipitScripts, shipitDevDeps, options = {}) {
|
|
17
|
+
const { dryRun = false, verbose = false } = options;
|
|
18
|
+
|
|
19
|
+
if (!existsSync(existingPath)) {
|
|
20
|
+
throw new Error(`package.json not found: ${existingPath}`);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// Read existing package.json
|
|
24
|
+
let existing;
|
|
25
|
+
try {
|
|
26
|
+
const content = readFileSync(existingPath, 'utf-8');
|
|
27
|
+
existing = JSON.parse(content);
|
|
28
|
+
} catch (error) {
|
|
29
|
+
if (error instanceof SyntaxError) {
|
|
30
|
+
throw new Error(`Invalid JSON in ${existingPath}: ${error.message}`);
|
|
31
|
+
}
|
|
32
|
+
throw error;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// Preserve user fields
|
|
36
|
+
const merged = {
|
|
37
|
+
...existing,
|
|
38
|
+
// Preserve these fields from existing
|
|
39
|
+
name: existing.name,
|
|
40
|
+
version: existing.version,
|
|
41
|
+
description: existing.description,
|
|
42
|
+
author: existing.author,
|
|
43
|
+
license: existing.license,
|
|
44
|
+
repository: existing.repository,
|
|
45
|
+
homepage: existing.homepage,
|
|
46
|
+
bugs: existing.bugs,
|
|
47
|
+
dependencies: existing.dependencies || {},
|
|
48
|
+
peerDependencies: existing.peerDependencies || {},
|
|
49
|
+
optionalDependencies: existing.optionalDependencies || {},
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
// Merge scripts
|
|
53
|
+
const existingScripts = existing.scripts || {};
|
|
54
|
+
const mergedScripts = { ...existingScripts };
|
|
55
|
+
|
|
56
|
+
for (const [scriptName, scriptCommand] of Object.entries(shipitScripts)) {
|
|
57
|
+
if (existingScripts[scriptName]) {
|
|
58
|
+
// Collision detected - prefix ShipIt script
|
|
59
|
+
const prefixedName = `shipit:${scriptName}`;
|
|
60
|
+
if (!existingScripts[prefixedName] && !existingScripts[`shipit:${scriptName}`]) {
|
|
61
|
+
mergedScripts[prefixedName] = scriptCommand;
|
|
62
|
+
if (verbose) {
|
|
63
|
+
console.log(` Script collision: ${scriptName} → ${prefixedName}`);
|
|
64
|
+
}
|
|
65
|
+
} else {
|
|
66
|
+
// Already prefixed or user has shipit: prefix, skip
|
|
67
|
+
if (verbose) {
|
|
68
|
+
console.log(` Skipping ${scriptName} (already customized)`);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
} else {
|
|
72
|
+
// No collision, add directly
|
|
73
|
+
mergedScripts[scriptName] = scriptCommand;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
merged.scripts = mergedScripts;
|
|
78
|
+
|
|
79
|
+
// Merge devDependencies
|
|
80
|
+
const existingDevDeps = existing.devDependencies || {};
|
|
81
|
+
const mergedDevDeps = { ...existingDevDeps };
|
|
82
|
+
|
|
83
|
+
for (const [depName, depVersion] of Object.entries(shipitDevDeps)) {
|
|
84
|
+
if (!existingDevDeps[depName]) {
|
|
85
|
+
// Add if missing
|
|
86
|
+
mergedDevDeps[depName] = depVersion;
|
|
87
|
+
} else {
|
|
88
|
+
// Keep existing version (user may have customized)
|
|
89
|
+
if (verbose) {
|
|
90
|
+
console.log(` Keeping existing devDependency: ${depName}@${existingDevDeps[depName]}`);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
merged.devDependencies = mergedDevDeps;
|
|
96
|
+
|
|
97
|
+
// Write merged package.json
|
|
98
|
+
if (!dryRun) {
|
|
99
|
+
const formatted = JSON.stringify(merged, null, 2);
|
|
100
|
+
writeFileSync(existingPath, formatted + '\n', 'utf-8');
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
return {
|
|
104
|
+
merged,
|
|
105
|
+
collisions: Object.keys(shipitScripts).filter(name => existingScripts[name]),
|
|
106
|
+
addedScripts: Object.keys(shipitScripts).filter(name => !existingScripts[name]),
|
|
107
|
+
addedDevDeps: Object.keys(shipitDevDeps).filter(name => !existingDevDeps[name])
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Get ShipIt scripts template
|
|
113
|
+
* @returns {object} ShipIt scripts object
|
|
114
|
+
*/
|
|
115
|
+
export function getShipitScripts() {
|
|
116
|
+
// These are the scripts that ShipIt adds to package.json
|
|
117
|
+
// Based on scripts/init-project.sh package.json template
|
|
118
|
+
return {
|
|
119
|
+
'new-intent': './scripts/new-intent.sh',
|
|
120
|
+
'scope-project': './scripts/scope-project.sh',
|
|
121
|
+
'generate-roadmap': './scripts/generate-roadmap.sh',
|
|
122
|
+
'generate-release-plan': './scripts/generate-release-plan.sh',
|
|
123
|
+
'drift-check': './scripts/drift-check.sh',
|
|
124
|
+
'deploy': './scripts/deploy.sh',
|
|
125
|
+
'check-readiness': './scripts/check-readiness.sh',
|
|
126
|
+
'workflow-orchestrator': './scripts/workflow-orchestrator.sh',
|
|
127
|
+
'kill-intent': './scripts/kill-intent.sh',
|
|
128
|
+
'verify': './scripts/verify.sh',
|
|
129
|
+
'fix': './scripts/fix-intents.sh',
|
|
130
|
+
'gh-create-issue': './scripts/gh/create-issue-from-intent.sh',
|
|
131
|
+
'gh-link-issue': './scripts/gh/link-issue.sh',
|
|
132
|
+
'gh-create-pr': './scripts/gh/create-pr.sh',
|
|
133
|
+
'on-ship-update-issue': './scripts/gh/on-ship-update-issue.sh',
|
|
134
|
+
'create-intent-from-issue': './scripts/gh/create-intent-from-issue.sh',
|
|
135
|
+
'help': './scripts/help.sh',
|
|
136
|
+
'status': './scripts/status.sh',
|
|
137
|
+
'suggest': './scripts/suggest.sh',
|
|
138
|
+
'dashboard': './scripts/dashboard-start.sh',
|
|
139
|
+
'execute-rollback': './scripts/execute-rollback.sh',
|
|
140
|
+
'export-dashboard-json': 'node scripts/export-dashboard-json.js'
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Get ShipIt devDependencies template
|
|
146
|
+
* @returns {object} ShipIt devDependencies object
|
|
147
|
+
*/
|
|
148
|
+
export function getShipitDevDependencies() {
|
|
149
|
+
// Based on scripts/init-project.sh package.json template
|
|
150
|
+
return {
|
|
151
|
+
'@types/node': '^20.10.0',
|
|
152
|
+
'@typescript-eslint/eslint-plugin': '^6.15.0',
|
|
153
|
+
'@typescript-eslint/parser': '^6.15.0',
|
|
154
|
+
'@stryker-mutator/core': '^8.0.0',
|
|
155
|
+
'@stryker-mutator/vitest-runner': '^8.0.0',
|
|
156
|
+
'@vitest/coverage-v8': '^1.0.4',
|
|
157
|
+
'eslint': '^8.56.0',
|
|
158
|
+
'prettier': '^3.1.1',
|
|
159
|
+
'tsx': '^4.7.0',
|
|
160
|
+
'typescript': '^5.3.3',
|
|
161
|
+
'vitest': '^1.0.4'
|
|
162
|
+
};
|
|
163
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* project.json merge for upgrade - preserve user fields, update shipitVersion
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { readFileSync, writeFileSync, existsSync } from 'fs';
|
|
6
|
+
import { join } from 'path';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Merge project.json: preserve user fields, update shipitVersion and settings defaults
|
|
10
|
+
* @param {string} projectPath - Project root
|
|
11
|
+
* @param {string} newShipitVersion - Version to set
|
|
12
|
+
* @param {object} options - { dryRun, verbose }
|
|
13
|
+
* @returns {object} Merge result
|
|
14
|
+
*/
|
|
15
|
+
export function mergeProjectJson(projectPath, newShipitVersion, options = {}) {
|
|
16
|
+
const { dryRun = false, verbose = false } = options;
|
|
17
|
+
const projectJsonPath = join(projectPath, 'project.json');
|
|
18
|
+
|
|
19
|
+
if (!existsSync(projectJsonPath)) {
|
|
20
|
+
if (verbose) console.log(' project.json not found, skipping merge');
|
|
21
|
+
return { merged: null, updated: false };
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
let existing;
|
|
25
|
+
try {
|
|
26
|
+
existing = JSON.parse(readFileSync(projectJsonPath, 'utf-8'));
|
|
27
|
+
} catch (e) {
|
|
28
|
+
throw new Error(`Invalid JSON in project.json: ${e.message}`);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const merged = {
|
|
32
|
+
...existing,
|
|
33
|
+
name: existing.name,
|
|
34
|
+
description: existing.description,
|
|
35
|
+
version: existing.version,
|
|
36
|
+
techStack: existing.techStack,
|
|
37
|
+
created: existing.created,
|
|
38
|
+
highRiskDomains: existing.highRiskDomains ?? [],
|
|
39
|
+
shipitVersion: newShipitVersion,
|
|
40
|
+
settings: {
|
|
41
|
+
humanResponseTime: existing.settings?.humanResponseTime ?? 'minutes',
|
|
42
|
+
confidenceThreshold: existing.settings?.confidenceThreshold ?? 0.7,
|
|
43
|
+
testCoverageMinimum: existing.settings?.testCoverageMinimum ?? 80,
|
|
44
|
+
...(existing.settings || {})
|
|
45
|
+
}
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
if (!dryRun) {
|
|
49
|
+
writeFileSync(
|
|
50
|
+
projectJsonPath,
|
|
51
|
+
JSON.stringify(merged, null, 2) + '\n',
|
|
52
|
+
'utf-8'
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return { merged, updated: true };
|
|
57
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Prompt utilities for interactive mode
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { createInterface } from 'readline';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Create readline interface
|
|
9
|
+
* @returns {object} Readline interface
|
|
10
|
+
*/
|
|
11
|
+
export function createReadlineInterface() {
|
|
12
|
+
return createInterface({
|
|
13
|
+
input: process.stdin,
|
|
14
|
+
output: process.stdout
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Prompt user for input
|
|
20
|
+
* @param {object} rl - Readline interface
|
|
21
|
+
* @param {string} question - Question to ask
|
|
22
|
+
* @returns {Promise<string>} User input
|
|
23
|
+
*/
|
|
24
|
+
export function promptUser(rl, question) {
|
|
25
|
+
return new Promise((resolve) => {
|
|
26
|
+
rl.question(question, (answer) => {
|
|
27
|
+
resolve(answer);
|
|
28
|
+
});
|
|
29
|
+
});
|
|
30
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Stack detection utilities
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { existsSync, readFileSync } from 'fs';
|
|
6
|
+
import { join } from 'path';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Detect tech stack from existing files
|
|
10
|
+
* @param {string} projectPath - Path to project directory
|
|
11
|
+
* @returns {string|null} Detected stack or null if ambiguous/unknown
|
|
12
|
+
*/
|
|
13
|
+
export function detectTechStack(projectPath) {
|
|
14
|
+
const packageJsonPath = join(projectPath, 'package.json');
|
|
15
|
+
const pyprojectTomlPath = join(projectPath, 'pyproject.toml');
|
|
16
|
+
const requirementsTxtPath = join(projectPath, 'requirements.txt');
|
|
17
|
+
const setupPyPath = join(projectPath, 'setup.py');
|
|
18
|
+
|
|
19
|
+
const hasPackageJson = existsSync(packageJsonPath);
|
|
20
|
+
const hasPyprojectToml = existsSync(pyprojectTomlPath);
|
|
21
|
+
const hasRequirementsTxt = existsSync(requirementsTxtPath);
|
|
22
|
+
const hasSetupPy = existsSync(setupPyPath);
|
|
23
|
+
|
|
24
|
+
// Check for ambiguous case (both Node and Python indicators)
|
|
25
|
+
if (hasPackageJson && (hasPyprojectToml || hasRequirementsTxt || hasSetupPy)) {
|
|
26
|
+
return null; // Ambiguous, prompt user
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// Validate package.json if it exists
|
|
30
|
+
if (hasPackageJson) {
|
|
31
|
+
try {
|
|
32
|
+
const content = readFileSync(packageJsonPath, 'utf-8');
|
|
33
|
+
JSON.parse(content); // Validate JSON
|
|
34
|
+
return 'typescript-nodejs';
|
|
35
|
+
} catch (e) {
|
|
36
|
+
return null; // Invalid JSON, prompt user
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Check for Python indicators
|
|
41
|
+
if (hasPyprojectToml || hasRequirementsTxt || hasSetupPy) {
|
|
42
|
+
return 'python';
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Empty directory or no indicators
|
|
46
|
+
return null; // Prompt user
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Validate stack value
|
|
51
|
+
* @param {string} stack - Stack value to validate
|
|
52
|
+
* @returns {boolean} True if valid
|
|
53
|
+
*/
|
|
54
|
+
export function isValidStack(stack) {
|
|
55
|
+
return ['typescript-nodejs', 'python', 'other'].includes(stack);
|
|
56
|
+
}
|