@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,364 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Stack-specific file creation utilities
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { writeFileSync, existsSync, mkdirSync } from 'fs';
|
|
6
|
+
import { join } from 'path';
|
|
7
|
+
import fsExtra from 'fs-extra';
|
|
8
|
+
|
|
9
|
+
const { copySync } = fsExtra;
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Create TypeScript/Node.js project files
|
|
13
|
+
* @param {string} projectPath - Project directory path
|
|
14
|
+
* @param {string} projectName - Project name
|
|
15
|
+
* @param {string} description - Project description
|
|
16
|
+
* @param {object} shipitScripts - ShipIt scripts to include
|
|
17
|
+
* @param {object} shipitDevDeps - ShipIt devDependencies to include
|
|
18
|
+
*/
|
|
19
|
+
export function createTypeScriptNodeFiles(projectPath, projectName, description, shipitScripts, shipitDevDeps) {
|
|
20
|
+
const normalizedName = projectName.toLowerCase().replace(/\s+/g, '-');
|
|
21
|
+
|
|
22
|
+
// Create package.json
|
|
23
|
+
const packageJson = {
|
|
24
|
+
name: normalizedName,
|
|
25
|
+
version: '0.1.0',
|
|
26
|
+
description: description,
|
|
27
|
+
type: 'module',
|
|
28
|
+
scripts: {
|
|
29
|
+
test: 'vitest run',
|
|
30
|
+
'test:watch': 'vitest',
|
|
31
|
+
'test:coverage': 'vitest run --coverage',
|
|
32
|
+
'test:mutate': 'stryker run',
|
|
33
|
+
lint: 'eslint . --ext .ts',
|
|
34
|
+
typecheck: 'tsc --noEmit',
|
|
35
|
+
build: 'tsc',
|
|
36
|
+
dev: 'tsx watch src/index.ts',
|
|
37
|
+
...shipitScripts
|
|
38
|
+
},
|
|
39
|
+
keywords: [],
|
|
40
|
+
author: '',
|
|
41
|
+
license: 'MIT',
|
|
42
|
+
devDependencies: {
|
|
43
|
+
'@types/node': '^20.10.0',
|
|
44
|
+
'@typescript-eslint/eslint-plugin': '^6.15.0',
|
|
45
|
+
'@typescript-eslint/parser': '^6.15.0',
|
|
46
|
+
'@stryker-mutator/core': '^8.0.0',
|
|
47
|
+
'@stryker-mutator/vitest-runner': '^8.0.0',
|
|
48
|
+
'@vitest/coverage-v8': '^1.0.4',
|
|
49
|
+
eslint: '^8.56.0',
|
|
50
|
+
prettier: '^3.1.1',
|
|
51
|
+
tsx: '^4.7.0',
|
|
52
|
+
typescript: '^5.3.3',
|
|
53
|
+
vitest: '^1.0.4',
|
|
54
|
+
...shipitDevDeps
|
|
55
|
+
},
|
|
56
|
+
dependencies: {}
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
writeFileSync(
|
|
60
|
+
join(projectPath, 'package.json'),
|
|
61
|
+
JSON.stringify(packageJson, null, 2) + '\n',
|
|
62
|
+
'utf-8'
|
|
63
|
+
);
|
|
64
|
+
|
|
65
|
+
// Create tsconfig.json
|
|
66
|
+
const tsconfig = {
|
|
67
|
+
compilerOptions: {
|
|
68
|
+
target: 'ES2022',
|
|
69
|
+
module: 'ESNext',
|
|
70
|
+
lib: ['ES2022'],
|
|
71
|
+
moduleResolution: 'node',
|
|
72
|
+
strict: true,
|
|
73
|
+
esModuleInterop: true,
|
|
74
|
+
skipLibCheck: true,
|
|
75
|
+
forceConsistentCasingInFileNames: true,
|
|
76
|
+
resolveJsonModule: true,
|
|
77
|
+
outDir: './dist',
|
|
78
|
+
rootDir: './src',
|
|
79
|
+
declaration: true,
|
|
80
|
+
declarationMap: true,
|
|
81
|
+
sourceMap: true,
|
|
82
|
+
noUnusedLocals: true,
|
|
83
|
+
noUnusedParameters: true,
|
|
84
|
+
noImplicitReturns: true,
|
|
85
|
+
noFallthroughCasesInSwitch: true
|
|
86
|
+
},
|
|
87
|
+
include: ['src/**/*'],
|
|
88
|
+
exclude: ['node_modules', 'dist', '**/*.test.ts', '**/*.spec.ts']
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
writeFileSync(
|
|
92
|
+
join(projectPath, 'tsconfig.json'),
|
|
93
|
+
JSON.stringify(tsconfig, null, 2) + '\n',
|
|
94
|
+
'utf-8'
|
|
95
|
+
);
|
|
96
|
+
|
|
97
|
+
// Create tsconfig.eslint.json
|
|
98
|
+
const tsconfigEslint = {
|
|
99
|
+
extends: './tsconfig.json',
|
|
100
|
+
include: ['src/**/*.ts', 'tests/**/*.ts', 'scripts/**/*.ts', '*.config.ts'],
|
|
101
|
+
exclude: ['node_modules', 'dist']
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
writeFileSync(
|
|
105
|
+
join(projectPath, 'tsconfig.eslint.json'),
|
|
106
|
+
JSON.stringify(tsconfigEslint, null, 2) + '\n',
|
|
107
|
+
'utf-8'
|
|
108
|
+
);
|
|
109
|
+
|
|
110
|
+
// Create .eslintrc.json
|
|
111
|
+
const eslintrc = {
|
|
112
|
+
root: true,
|
|
113
|
+
parser: '@typescript-eslint/parser',
|
|
114
|
+
parserOptions: {
|
|
115
|
+
ecmaVersion: 2022,
|
|
116
|
+
sourceType: 'module',
|
|
117
|
+
project: ['./tsconfig.eslint.json'],
|
|
118
|
+
tsconfigRootDir: '.'
|
|
119
|
+
},
|
|
120
|
+
extends: [
|
|
121
|
+
'eslint:recommended',
|
|
122
|
+
'plugin:@typescript-eslint/recommended',
|
|
123
|
+
'plugin:@typescript-eslint/strict-type-checked'
|
|
124
|
+
],
|
|
125
|
+
plugins: ['@typescript-eslint'],
|
|
126
|
+
rules: {
|
|
127
|
+
'@typescript-eslint/no-explicit-any': 'error',
|
|
128
|
+
'@typescript-eslint/ban-types': 'error',
|
|
129
|
+
'no-eval': 'error',
|
|
130
|
+
'no-console': ['error', { allow: ['warn', 'error'] }]
|
|
131
|
+
},
|
|
132
|
+
overrides: [
|
|
133
|
+
{
|
|
134
|
+
files: ['scripts/**/*.ts', 'tests/**/*.ts', '**/*.config.ts'],
|
|
135
|
+
rules: {
|
|
136
|
+
'no-console': 'off',
|
|
137
|
+
'@typescript-eslint/no-unsafe-assignment': 'off',
|
|
138
|
+
'@typescript-eslint/no-unsafe-call': 'off',
|
|
139
|
+
'@typescript-eslint/no-unsafe-member-access': 'off',
|
|
140
|
+
'@typescript-eslint/no-unsafe-return': 'off',
|
|
141
|
+
'@typescript-eslint/no-unsafe-argument': 'off'
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
],
|
|
145
|
+
ignorePatterns: ['dist', 'node_modules', '*.config.js']
|
|
146
|
+
};
|
|
147
|
+
|
|
148
|
+
writeFileSync(
|
|
149
|
+
join(projectPath, '.eslintrc.json'),
|
|
150
|
+
JSON.stringify(eslintrc, null, 2) + '\n',
|
|
151
|
+
'utf-8'
|
|
152
|
+
);
|
|
153
|
+
|
|
154
|
+
// Create vitest.config.ts
|
|
155
|
+
const vitestConfig = `import { defineConfig } from 'vitest/config';
|
|
156
|
+
|
|
157
|
+
export default defineConfig({
|
|
158
|
+
test: {
|
|
159
|
+
globals: true,
|
|
160
|
+
environment: 'node',
|
|
161
|
+
include: ['tests/**/*.test.ts'],
|
|
162
|
+
coverage: {
|
|
163
|
+
provider: 'v8',
|
|
164
|
+
reporter: ['text', 'json', 'html', 'lcov'],
|
|
165
|
+
exclude: [
|
|
166
|
+
'node_modules/',
|
|
167
|
+
'dist/',
|
|
168
|
+
'tests/',
|
|
169
|
+
'**/*.test.ts',
|
|
170
|
+
'**/*.spec.ts',
|
|
171
|
+
'**/*.config.ts',
|
|
172
|
+
],
|
|
173
|
+
},
|
|
174
|
+
},
|
|
175
|
+
});
|
|
176
|
+
`;
|
|
177
|
+
|
|
178
|
+
writeFileSync(join(projectPath, 'vitest.config.ts'), vitestConfig, 'utf-8');
|
|
179
|
+
|
|
180
|
+
// Create .npmrc
|
|
181
|
+
writeFileSync(join(projectPath, '.npmrc'), 'audit-level=high\n', 'utf-8');
|
|
182
|
+
|
|
183
|
+
// Create src/index.ts
|
|
184
|
+
const srcDir = join(projectPath, 'src');
|
|
185
|
+
if (!existsSync(srcDir)) {
|
|
186
|
+
mkdirSync(srcDir, { recursive: true });
|
|
187
|
+
}
|
|
188
|
+
const indexTs = `// ${projectName}
|
|
189
|
+
// ${description}
|
|
190
|
+
|
|
191
|
+
export const projectName = '${projectName}';
|
|
192
|
+
export const projectDescription = '${description}';
|
|
193
|
+
`;
|
|
194
|
+
writeFileSync(join(srcDir, 'index.ts'), indexTs, 'utf-8');
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* Create Python project files
|
|
199
|
+
* @param {string} projectPath - Project directory path
|
|
200
|
+
* @param {string} projectName - Project name
|
|
201
|
+
* @param {string} description - Project description
|
|
202
|
+
*/
|
|
203
|
+
export function createPythonFiles(projectPath, projectName, description) {
|
|
204
|
+
// Create pyproject.toml if not exists
|
|
205
|
+
const pyprojectPath = join(projectPath, 'pyproject.toml');
|
|
206
|
+
if (!existsSync(pyprojectPath)) {
|
|
207
|
+
const pyprojectContent = `[project]
|
|
208
|
+
name = "${projectName}"
|
|
209
|
+
description = "${description}"
|
|
210
|
+
version = "0.1.0"
|
|
211
|
+
requires-python = ">=3.10"
|
|
212
|
+
|
|
213
|
+
[project.optional-dependencies]
|
|
214
|
+
dev = [
|
|
215
|
+
"pytest>=7.0.0",
|
|
216
|
+
"ruff>=0.1.0",
|
|
217
|
+
"mypy>=1.0.0",
|
|
218
|
+
]
|
|
219
|
+
`;
|
|
220
|
+
writeFileSync(pyprojectPath, pyprojectContent, 'utf-8');
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
// Create requirements.txt if not exists
|
|
224
|
+
const requirementsPath = join(projectPath, 'requirements.txt');
|
|
225
|
+
if (!existsSync(requirementsPath)) {
|
|
226
|
+
writeFileSync(requirementsPath, '# Project dependencies\n', 'utf-8');
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* Create Other stack project files
|
|
232
|
+
* @param {string} projectPath - Project directory path
|
|
233
|
+
*/
|
|
234
|
+
export function createOtherStackFiles(projectPath) {
|
|
235
|
+
// Create minimal .gitignore
|
|
236
|
+
const gitignorePath = join(projectPath, '.gitignore');
|
|
237
|
+
if (!existsSync(gitignorePath)) {
|
|
238
|
+
writeFileSync(gitignorePath, '*.log\n.DS_Store\n.env\n', 'utf-8');
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
// Create src directory
|
|
242
|
+
const srcPath = join(projectPath, 'src');
|
|
243
|
+
if (!existsSync(srcPath)) {
|
|
244
|
+
mkdirSync(srcPath, { recursive: true });
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
/**
|
|
249
|
+
* Create stack-specific CI workflow
|
|
250
|
+
* @param {string} projectPath - Project directory path
|
|
251
|
+
* @param {string} stack - Tech stack (typescript-nodejs | python | other)
|
|
252
|
+
*/
|
|
253
|
+
export function createCIWorkflow(projectPath, stack) {
|
|
254
|
+
const workflowsDir = join(projectPath, '.github', 'workflows');
|
|
255
|
+
if (!existsSync(workflowsDir)) {
|
|
256
|
+
mkdirSync(workflowsDir, { recursive: true });
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
const ciPath = join(workflowsDir, 'ci.yml');
|
|
260
|
+
|
|
261
|
+
let ciContent = '';
|
|
262
|
+
|
|
263
|
+
if (stack === 'typescript-nodejs') {
|
|
264
|
+
ciContent = `name: CI
|
|
265
|
+
|
|
266
|
+
on:
|
|
267
|
+
push:
|
|
268
|
+
branches: [main, develop]
|
|
269
|
+
pull_request:
|
|
270
|
+
branches: [main, develop]
|
|
271
|
+
|
|
272
|
+
jobs:
|
|
273
|
+
lint:
|
|
274
|
+
name: Lint & Type Check
|
|
275
|
+
runs-on: ubuntu-latest
|
|
276
|
+
steps:
|
|
277
|
+
- uses: actions/checkout@v4
|
|
278
|
+
- uses: pnpm/action-setup@v2
|
|
279
|
+
with:
|
|
280
|
+
version: 10.11.0
|
|
281
|
+
- uses: actions/setup-node@v4
|
|
282
|
+
with:
|
|
283
|
+
node-version: '20'
|
|
284
|
+
cache: 'pnpm'
|
|
285
|
+
- run: pnpm install --frozen-lockfile
|
|
286
|
+
- run: pnpm lint
|
|
287
|
+
- run: pnpm typecheck
|
|
288
|
+
|
|
289
|
+
test:
|
|
290
|
+
name: Test Suite
|
|
291
|
+
runs-on: ubuntu-latest
|
|
292
|
+
steps:
|
|
293
|
+
- uses: actions/checkout@v4
|
|
294
|
+
- uses: pnpm/action-setup@v2
|
|
295
|
+
with:
|
|
296
|
+
version: 10.11.0
|
|
297
|
+
- uses: actions/setup-node@v4
|
|
298
|
+
with:
|
|
299
|
+
node-version: '20'
|
|
300
|
+
cache: 'pnpm'
|
|
301
|
+
- run: pnpm install --frozen-lockfile
|
|
302
|
+
- run: pnpm test:coverage
|
|
303
|
+
- name: Upload coverage
|
|
304
|
+
uses: codecov/codecov-action@v3
|
|
305
|
+
if: always()
|
|
306
|
+
with:
|
|
307
|
+
files: ./coverage/lcov.info
|
|
308
|
+
fail_ci_if_error: false
|
|
309
|
+
`;
|
|
310
|
+
} else if (stack === 'python') {
|
|
311
|
+
ciContent = `name: CI
|
|
312
|
+
|
|
313
|
+
on:
|
|
314
|
+
push:
|
|
315
|
+
branches: [main, develop]
|
|
316
|
+
pull_request:
|
|
317
|
+
branches: [main, develop]
|
|
318
|
+
|
|
319
|
+
jobs:
|
|
320
|
+
lint:
|
|
321
|
+
name: Lint & Type Check
|
|
322
|
+
runs-on: ubuntu-latest
|
|
323
|
+
steps:
|
|
324
|
+
- uses: actions/checkout@v4
|
|
325
|
+
- uses: actions/setup-python@v4
|
|
326
|
+
with:
|
|
327
|
+
python-version: '3.10'
|
|
328
|
+
- run: pip install -r requirements.txt
|
|
329
|
+
- run: ruff check .
|
|
330
|
+
- run: mypy .
|
|
331
|
+
|
|
332
|
+
test:
|
|
333
|
+
name: Test Suite
|
|
334
|
+
runs-on: ubuntu-latest
|
|
335
|
+
steps:
|
|
336
|
+
- uses: actions/checkout@v4
|
|
337
|
+
- uses: actions/setup-python@v4
|
|
338
|
+
with:
|
|
339
|
+
python-version: '3.10'
|
|
340
|
+
- run: pip install -r requirements.txt
|
|
341
|
+
- run: pytest --cov
|
|
342
|
+
`;
|
|
343
|
+
} else {
|
|
344
|
+
// Other stack - minimal template
|
|
345
|
+
ciContent = `name: CI
|
|
346
|
+
|
|
347
|
+
on:
|
|
348
|
+
push:
|
|
349
|
+
branches: [main, develop]
|
|
350
|
+
pull_request:
|
|
351
|
+
branches: [main, develop]
|
|
352
|
+
|
|
353
|
+
jobs:
|
|
354
|
+
build:
|
|
355
|
+
name: Build
|
|
356
|
+
runs-on: ubuntu-latest
|
|
357
|
+
steps:
|
|
358
|
+
- uses: actions/checkout@v4
|
|
359
|
+
- run: echo "Add your build steps here"
|
|
360
|
+
`;
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
writeFileSync(ciPath, ciContent, 'utf-8');
|
|
364
|
+
}
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Upgrade backup and file-modification detection
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { readFileSync, existsSync, readdirSync, statSync } from 'fs';
|
|
6
|
+
import { join, dirname } from 'path';
|
|
7
|
+
import { createHash } from 'crypto';
|
|
8
|
+
import fsExtra from 'fs-extra';
|
|
9
|
+
|
|
10
|
+
const { copySync, mkdirSync } = fsExtra;
|
|
11
|
+
|
|
12
|
+
const BACKUP_DIR = '._shipit_backup';
|
|
13
|
+
const TIMESTAMP_FORMAT = (d) => d.toISOString().replace(/[-:]/g, '').slice(0, 15); // YYYYMMDDTHHMMSS
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Calculate SHA-256 hash of file
|
|
17
|
+
* @param {string} filePath - Absolute path
|
|
18
|
+
* @returns {string|null} Hex hash or null if not found
|
|
19
|
+
*/
|
|
20
|
+
export function calculateFileHash(filePath) {
|
|
21
|
+
if (!existsSync(filePath)) return null;
|
|
22
|
+
try {
|
|
23
|
+
const content = readFileSync(filePath);
|
|
24
|
+
return createHash('sha256').update(content).digest('hex');
|
|
25
|
+
} catch {
|
|
26
|
+
return null;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Check if project file differs from framework file
|
|
32
|
+
* @param {string} projectPath - Project root
|
|
33
|
+
* @param {string} frameworkRoot - Framework root
|
|
34
|
+
* @param {string} relativeFilePath - Path relative to root (e.g. 'scripts/verify.sh')
|
|
35
|
+
* @returns {boolean} True if file is modified (different from framework)
|
|
36
|
+
*/
|
|
37
|
+
export function isFileModified(projectPath, frameworkRoot, relativeFilePath) {
|
|
38
|
+
const projectFile = join(projectPath, relativeFilePath);
|
|
39
|
+
const frameworkFile = join(frameworkRoot, relativeFilePath);
|
|
40
|
+
if (!existsSync(projectFile)) return false;
|
|
41
|
+
if (!existsSync(frameworkFile)) return false;
|
|
42
|
+
const projectHash = calculateFileHash(projectFile);
|
|
43
|
+
const frameworkHash = calculateFileHash(frameworkFile);
|
|
44
|
+
return projectHash !== frameworkHash;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Backup a file to ._shipit_backup/
|
|
49
|
+
* @param {string} projectPath - Project root
|
|
50
|
+
* @param {string} relativeFilePath - Path relative to project (e.g. 'scripts/verify.sh')
|
|
51
|
+
* @param {string} backupDir - Backup directory name (default ._shipit_backup)
|
|
52
|
+
* @returns {string} Path to backup file
|
|
53
|
+
*/
|
|
54
|
+
export function backupFile(projectPath, relativeFilePath, backupDir = BACKUP_DIR) {
|
|
55
|
+
const src = join(projectPath, relativeFilePath);
|
|
56
|
+
if (!existsSync(src)) throw new Error(`File not found: ${relativeFilePath}`);
|
|
57
|
+
const timestamp = TIMESTAMP_FORMAT(new Date());
|
|
58
|
+
const backupRelative = `${relativeFilePath}.${timestamp}`;
|
|
59
|
+
const backupFull = join(projectPath, backupDir, backupRelative);
|
|
60
|
+
const backupParent = join(backupFull, '..');
|
|
61
|
+
mkdirSync(backupParent, { recursive: true });
|
|
62
|
+
copySync(src, backupFull, { overwrite: true });
|
|
63
|
+
return backupFull;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* List backups in project's backup directory
|
|
68
|
+
* @param {string} projectPath - Project root
|
|
69
|
+
* @param {string} backupDir - Backup directory name
|
|
70
|
+
* @returns {Array<{path: string, original: string, timestamp: string}>}
|
|
71
|
+
*/
|
|
72
|
+
export function listBackups(projectPath, backupDir = BACKUP_DIR) {
|
|
73
|
+
const dir = join(projectPath, backupDir);
|
|
74
|
+
if (!existsSync(dir)) return [];
|
|
75
|
+
const out = [];
|
|
76
|
+
function walk(current, prefix = '') {
|
|
77
|
+
const entries = readdirSync(current, { withFileTypes: true });
|
|
78
|
+
for (const e of entries) {
|
|
79
|
+
const rel = prefix ? `${prefix}/${e.name}` : e.name;
|
|
80
|
+
const full = join(current, e.name);
|
|
81
|
+
if (e.isDirectory()) walk(full, rel);
|
|
82
|
+
else if (e.isFile() && /\.\d{8}T\d{6}$/.test(e.name)) {
|
|
83
|
+
const original = rel.replace(/\.\d{8}T\d{6}$/, '');
|
|
84
|
+
const timestamp = e.name.slice(-15);
|
|
85
|
+
out.push({ path: full, original: join(projectPath, original), timestamp, relative: rel });
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
walk(dir);
|
|
90
|
+
return out.sort((a, b) => b.timestamp.localeCompare(a.timestamp));
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Restore a file from backup
|
|
95
|
+
* @param {string} backupFilePath - Full path to backup file
|
|
96
|
+
* @param {string|null} targetPath - Full path to restore to (if null, derived from backup path: remove backup dir and timestamp)
|
|
97
|
+
* @param {string} projectPath - Project root (required when targetPath is null)
|
|
98
|
+
* @param {string} backupDir - Backup directory name (required when targetPath is null)
|
|
99
|
+
* @param {boolean} removeBackup - Remove backup after restore
|
|
100
|
+
*/
|
|
101
|
+
export function restoreFromBackup(backupFilePath, targetPath, projectPath = null, backupDir = BACKUP_DIR, removeBackup = false) {
|
|
102
|
+
if (!existsSync(backupFilePath)) throw new Error(`Backup not found: ${backupFilePath}`);
|
|
103
|
+
let target = targetPath;
|
|
104
|
+
if (!target && projectPath) {
|
|
105
|
+
const backupPrefix = join(projectPath, backupDir);
|
|
106
|
+
const rel = backupFilePath.startsWith(backupPrefix)
|
|
107
|
+
? backupFilePath.slice(backupPrefix.length + 1).replace(/\.\d{8}T\d{6}$/, '')
|
|
108
|
+
: backupFilePath.replace(/\.\d{8}T\d{6}$/, '');
|
|
109
|
+
target = join(projectPath, rel);
|
|
110
|
+
} else if (!target) {
|
|
111
|
+
target = backupFilePath.replace(/\.\d{8}T\d{6}$/, '');
|
|
112
|
+
}
|
|
113
|
+
mkdirSync(dirname(target), { recursive: true });
|
|
114
|
+
copySync(backupFilePath, target, { overwrite: true });
|
|
115
|
+
if (removeBackup) fsExtra.removeSync(backupFilePath);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Collect all framework-owned file paths (relative) for upgrade
|
|
120
|
+
* @param {string} frameworkRoot - Framework root
|
|
121
|
+
* @param {object} manifest - Parsed framework-files-manifest
|
|
122
|
+
* @param {Set<string>} neverCopied - Set of paths to skip
|
|
123
|
+
* @returns {string[]} Relative file paths
|
|
124
|
+
*/
|
|
125
|
+
export function getFrameworkFileList(frameworkRoot, manifest, neverCopied) {
|
|
126
|
+
const files = new Set();
|
|
127
|
+
|
|
128
|
+
function addFile(rel) {
|
|
129
|
+
if (neverCopied.has(rel) || rel.startsWith('.override')) return;
|
|
130
|
+
const full = join(frameworkRoot, rel);
|
|
131
|
+
if (existsSync(full) && statSync(full).isFile()) files.add(rel);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
for (const dir of manifest.frameworkOwned?.directories || []) {
|
|
135
|
+
const fullDir = join(frameworkRoot, dir);
|
|
136
|
+
if (!existsSync(fullDir)) continue;
|
|
137
|
+
walkDir(fullDir, dir, (rel) => addFile(rel));
|
|
138
|
+
}
|
|
139
|
+
for (const file of manifest.frameworkOwned?.files || []) {
|
|
140
|
+
if (file === 'project.json') continue; // merged, not replaced
|
|
141
|
+
addFile(file);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
return [...files];
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
const SKIP_DIRS = new Set(['node_modules', '.git', 'dist', 'coverage', '.turbo']);
|
|
148
|
+
|
|
149
|
+
function walkDir(fullDir, prefix, add) {
|
|
150
|
+
const entries = readdirSync(fullDir, { withFileTypes: true });
|
|
151
|
+
for (const e of entries) {
|
|
152
|
+
const rel = prefix ? `${prefix}/${e.name}` : e.name;
|
|
153
|
+
const full = join(fullDir, e.name);
|
|
154
|
+
if (e.isDirectory()) {
|
|
155
|
+
if (SKIP_DIRS.has(e.name)) continue;
|
|
156
|
+
walkDir(full, rel, add);
|
|
157
|
+
} else if (e.isFile()) add(rel);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Version detection and comparison for ShipIt upgrade
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { readFileSync, existsSync } from 'fs';
|
|
6
|
+
import { join } from 'path';
|
|
7
|
+
import { getFrameworkRoot } from './manifest.js';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Get installed ShipIt version from package
|
|
11
|
+
* @returns {string} Version string (e.g. "1.0.0")
|
|
12
|
+
*/
|
|
13
|
+
export function getInstalledShipItVersion() {
|
|
14
|
+
const root = getFrameworkRoot();
|
|
15
|
+
const pkgPath = join(root, 'package.json');
|
|
16
|
+
if (!existsSync(pkgPath)) {
|
|
17
|
+
throw new Error('ShipIt not found. Install: npm install -g @nlaprell/shipit');
|
|
18
|
+
}
|
|
19
|
+
const pkg = JSON.parse(readFileSync(pkgPath, 'utf-8'));
|
|
20
|
+
return pkg.version || '0.0.0';
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Get project's ShipIt version from project.json
|
|
25
|
+
* @param {string} projectPath - Project root path
|
|
26
|
+
* @returns {string|null} Version or null if missing
|
|
27
|
+
*/
|
|
28
|
+
export function getProjectShipItVersion(projectPath) {
|
|
29
|
+
const projectJsonPath = join(projectPath, 'project.json');
|
|
30
|
+
if (!existsSync(projectJsonPath)) return null;
|
|
31
|
+
try {
|
|
32
|
+
const data = JSON.parse(readFileSync(projectJsonPath, 'utf-8'));
|
|
33
|
+
return data.shipitVersion ?? null;
|
|
34
|
+
} catch {
|
|
35
|
+
return null;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Compare two semver strings
|
|
41
|
+
* @param {string} installed - Installed version
|
|
42
|
+
* @param {string} project - Project version
|
|
43
|
+
* @returns {'same'|'newer'|'older'|'unknown'} Comparison result
|
|
44
|
+
*/
|
|
45
|
+
export function compareVersions(installed, project) {
|
|
46
|
+
if (!installed || !project) return 'unknown';
|
|
47
|
+
const a = parseVersion(installed);
|
|
48
|
+
const b = parseVersion(project);
|
|
49
|
+
if (!a || !b) return 'unknown';
|
|
50
|
+
if (a.major !== b.major) return a.major > b.major ? 'newer' : 'older';
|
|
51
|
+
if (a.minor !== b.minor) return a.minor > b.minor ? 'newer' : 'older';
|
|
52
|
+
if (a.patch !== b.patch) return a.patch > b.patch ? 'newer' : 'older';
|
|
53
|
+
return 'same';
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function parseVersion(v) {
|
|
57
|
+
const match = String(v).match(/^(\d+)\.(\d+)\.(\d+)/);
|
|
58
|
+
if (!match) return null;
|
|
59
|
+
return {
|
|
60
|
+
major: parseInt(match[1], 10),
|
|
61
|
+
minor: parseInt(match[2], 10),
|
|
62
|
+
patch: parseInt(match[3], 10)
|
|
63
|
+
};
|
|
64
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
# React + TypeScript + Vite
|
|
2
|
+
|
|
3
|
+
This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules.
|
|
4
|
+
|
|
5
|
+
Currently, two official plugins are available:
|
|
6
|
+
|
|
7
|
+
- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react) uses [Babel](https://babeljs.io/) (or [oxc](https://oxc.rs) when used in [rolldown-vite](https://vite.dev/guide/rolldown)) for Fast Refresh
|
|
8
|
+
- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh
|
|
9
|
+
|
|
10
|
+
## React Compiler
|
|
11
|
+
|
|
12
|
+
The React Compiler is not enabled on this template because of its impact on dev & build performances. To add it, see [this documentation](https://react.dev/learn/react-compiler/installation).
|
|
13
|
+
|
|
14
|
+
## Expanding the ESLint configuration
|
|
15
|
+
|
|
16
|
+
If you are developing a production application, we recommend updating the configuration to enable type-aware lint rules:
|
|
17
|
+
|
|
18
|
+
```js
|
|
19
|
+
export default defineConfig([
|
|
20
|
+
globalIgnores(['dist']),
|
|
21
|
+
{
|
|
22
|
+
files: ['**/*.{ts,tsx}'],
|
|
23
|
+
extends: [
|
|
24
|
+
// Other configs...
|
|
25
|
+
|
|
26
|
+
// Remove tseslint.configs.recommended and replace with this
|
|
27
|
+
tseslint.configs.recommendedTypeChecked,
|
|
28
|
+
// Alternatively, use this for stricter rules
|
|
29
|
+
tseslint.configs.strictTypeChecked,
|
|
30
|
+
// Optionally, add this for stylistic rules
|
|
31
|
+
tseslint.configs.stylisticTypeChecked,
|
|
32
|
+
|
|
33
|
+
// Other configs...
|
|
34
|
+
],
|
|
35
|
+
languageOptions: {
|
|
36
|
+
parserOptions: {
|
|
37
|
+
project: ['./tsconfig.node.json', './tsconfig.app.json'],
|
|
38
|
+
tsconfigRootDir: import.meta.dirname,
|
|
39
|
+
},
|
|
40
|
+
// other options...
|
|
41
|
+
},
|
|
42
|
+
},
|
|
43
|
+
]);
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
You can also install [eslint-plugin-react-x](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-x) and [eslint-plugin-react-dom](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-dom) for React-specific lint rules:
|
|
47
|
+
|
|
48
|
+
```js
|
|
49
|
+
// eslint.config.js
|
|
50
|
+
import reactX from 'eslint-plugin-react-x';
|
|
51
|
+
import reactDom from 'eslint-plugin-react-dom';
|
|
52
|
+
|
|
53
|
+
export default defineConfig([
|
|
54
|
+
globalIgnores(['dist']),
|
|
55
|
+
{
|
|
56
|
+
files: ['**/*.{ts,tsx}'],
|
|
57
|
+
extends: [
|
|
58
|
+
// Other configs...
|
|
59
|
+
// Enable lint rules for React
|
|
60
|
+
reactX.configs['recommended-typescript'],
|
|
61
|
+
// Enable lint rules for React DOM
|
|
62
|
+
reactDom.configs.recommended,
|
|
63
|
+
],
|
|
64
|
+
languageOptions: {
|
|
65
|
+
parserOptions: {
|
|
66
|
+
project: ['./tsconfig.node.json', './tsconfig.app.json'],
|
|
67
|
+
tsconfigRootDir: import.meta.dirname,
|
|
68
|
+
},
|
|
69
|
+
// other options...
|
|
70
|
+
},
|
|
71
|
+
},
|
|
72
|
+
]);
|
|
73
|
+
```
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import js from '@eslint/js'
|
|
2
|
+
import globals from 'globals'
|
|
3
|
+
import reactHooks from 'eslint-plugin-react-hooks'
|
|
4
|
+
import reactRefresh from 'eslint-plugin-react-refresh'
|
|
5
|
+
import tseslint from 'typescript-eslint'
|
|
6
|
+
import { defineConfig, globalIgnores } from 'eslint/config'
|
|
7
|
+
|
|
8
|
+
export default defineConfig([
|
|
9
|
+
globalIgnores(['dist']),
|
|
10
|
+
{
|
|
11
|
+
files: ['**/*.{ts,tsx}'],
|
|
12
|
+
extends: [
|
|
13
|
+
js.configs.recommended,
|
|
14
|
+
tseslint.configs.recommended,
|
|
15
|
+
reactHooks.configs.flat.recommended,
|
|
16
|
+
reactRefresh.configs.vite,
|
|
17
|
+
],
|
|
18
|
+
languageOptions: {
|
|
19
|
+
ecmaVersion: 2020,
|
|
20
|
+
globals: globals.browser,
|
|
21
|
+
},
|
|
22
|
+
},
|
|
23
|
+
])
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8" />
|
|
5
|
+
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
|
6
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
7
|
+
<title>ShipIt Dashboard</title>
|
|
8
|
+
</head>
|
|
9
|
+
<body>
|
|
10
|
+
<div id="root"></div>
|
|
11
|
+
<script type="module" src="/src/main.tsx"></script>
|
|
12
|
+
</body>
|
|
13
|
+
</html>
|