newstack 0.2.0__tar.gz → 0.3.0__tar.gz
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.
- {newstack-0.2.0 → newstack-0.3.0}/CHANGELOG.md +22 -0
- {newstack-0.2.0 → newstack-0.3.0}/PKG-INFO +42 -3
- {newstack-0.2.0 → newstack-0.3.0}/README.md +41 -2
- {newstack-0.2.0 → newstack-0.3.0}/pyproject.toml +1 -1
- {newstack-0.2.0 → newstack-0.3.0}/src/newstack/__init__.py +1 -1
- newstack-0.3.0/src/newstack/adopt.py +160 -0
- {newstack-0.2.0 → newstack-0.3.0}/src/newstack/cli.py +102 -10
- {newstack-0.2.0 → newstack-0.3.0}/src/newstack/data/stacks/django/stack.json +18 -0
- {newstack-0.2.0 → newstack-0.3.0}/src/newstack/data/stacks/fastapi/stack.json +15 -0
- {newstack-0.2.0 → newstack-0.3.0}/src/newstack/data/stacks/go-cli/stack.json +13 -0
- {newstack-0.2.0 → newstack-0.3.0}/src/newstack/data/stacks/node-express-ts/stack.json +15 -0
- {newstack-0.2.0 → newstack-0.3.0}/src/newstack/data/stacks/python-cli/stack.json +17 -0
- {newstack-0.2.0 → newstack-0.3.0}/src/newstack/data/stacks/python-lib/stack.json +13 -0
- {newstack-0.2.0 → newstack-0.3.0}/src/newstack/data/stacks/react-vite-ts/stack.json +16 -0
- {newstack-0.2.0 → newstack-0.3.0}/src/newstack/data/stacks/rust-cli/stack.json +13 -0
- {newstack-0.2.0 → newstack-0.3.0}/src/newstack/data/stacks/static-site/stack.json +13 -0
- {newstack-0.2.0 → newstack-0.3.0}/src/newstack/data/stacks/svelte-kit-ts/stack.json +16 -0
- {newstack-0.2.0 → newstack-0.3.0}/src/newstack/registry.py +50 -0
- {newstack-0.2.0 → newstack-0.3.0}/src/newstack/render.py +12 -0
- newstack-0.3.0/tests/test_adopt.py +192 -0
- {newstack-0.2.0 → newstack-0.3.0}/.gitignore +0 -0
- {newstack-0.2.0 → newstack-0.3.0}/LICENSE +0 -0
- {newstack-0.2.0 → newstack-0.3.0}/src/newstack/__main__.py +0 -0
- {newstack-0.2.0 → newstack-0.3.0}/src/newstack/data/stacks/django/template/.github/workflows/ci.yml +0 -0
- {newstack-0.2.0 → newstack-0.3.0}/src/newstack/data/stacks/django/template/.gitignore +0 -0
- {newstack-0.2.0 → newstack-0.3.0}/src/newstack/data/stacks/django/template/LICENSE +0 -0
- {newstack-0.2.0 → newstack-0.3.0}/src/newstack/data/stacks/django/template/README.md +0 -0
- {newstack-0.2.0 → newstack-0.3.0}/src/newstack/data/stacks/django/template/config/__init__.py +0 -0
- {newstack-0.2.0 → newstack-0.3.0}/src/newstack/data/stacks/django/template/config/asgi.py +0 -0
- {newstack-0.2.0 → newstack-0.3.0}/src/newstack/data/stacks/django/template/config/settings.py +0 -0
- {newstack-0.2.0 → newstack-0.3.0}/src/newstack/data/stacks/django/template/config/urls.py +0 -0
- {newstack-0.2.0 → newstack-0.3.0}/src/newstack/data/stacks/django/template/config/wsgi.py +0 -0
- {newstack-0.2.0 → newstack-0.3.0}/src/newstack/data/stacks/django/template/core/__init__.py +0 -0
- {newstack-0.2.0 → newstack-0.3.0}/src/newstack/data/stacks/django/template/core/apps.py +0 -0
- {newstack-0.2.0 → newstack-0.3.0}/src/newstack/data/stacks/django/template/core/migrations/0001_initial.py +0 -0
- {newstack-0.2.0 → newstack-0.3.0}/src/newstack/data/stacks/django/template/core/migrations/__init__.py +0 -0
- {newstack-0.2.0 → newstack-0.3.0}/src/newstack/data/stacks/django/template/core/models.py +0 -0
- {newstack-0.2.0 → newstack-0.3.0}/src/newstack/data/stacks/django/template/core/urls.py +0 -0
- {newstack-0.2.0 → newstack-0.3.0}/src/newstack/data/stacks/django/template/core/views.py +0 -0
- {newstack-0.2.0 → newstack-0.3.0}/src/newstack/data/stacks/django/template/manage.py +0 -0
- {newstack-0.2.0 → newstack-0.3.0}/src/newstack/data/stacks/django/template/pyproject.toml +0 -0
- {newstack-0.2.0 → newstack-0.3.0}/src/newstack/data/stacks/django/template/tests/test_models.py +0 -0
- {newstack-0.2.0 → newstack-0.3.0}/src/newstack/data/stacks/django/template/tests/test_views.py +0 -0
- {newstack-0.2.0 → newstack-0.3.0}/src/newstack/data/stacks/fastapi/template/.github/workflows/ci.yml +0 -0
- {newstack-0.2.0 → newstack-0.3.0}/src/newstack/data/stacks/fastapi/template/.gitignore +0 -0
- {newstack-0.2.0 → newstack-0.3.0}/src/newstack/data/stacks/fastapi/template/Dockerfile +0 -0
- {newstack-0.2.0 → newstack-0.3.0}/src/newstack/data/stacks/fastapi/template/LICENSE +0 -0
- {newstack-0.2.0 → newstack-0.3.0}/src/newstack/data/stacks/fastapi/template/README.md +0 -0
- {newstack-0.2.0 → newstack-0.3.0}/src/newstack/data/stacks/fastapi/template/pyproject.toml +0 -0
- {newstack-0.2.0 → newstack-0.3.0}/src/newstack/data/stacks/fastapi/template/src/{{package_name}}/__init__.py +0 -0
- {newstack-0.2.0 → newstack-0.3.0}/src/newstack/data/stacks/fastapi/template/src/{{package_name}}/main.py +0 -0
- {newstack-0.2.0 → newstack-0.3.0}/src/newstack/data/stacks/fastapi/template/tests/test_api.py +0 -0
- {newstack-0.2.0 → newstack-0.3.0}/src/newstack/data/stacks/go-cli/template/.github/workflows/ci.yml +0 -0
- {newstack-0.2.0 → newstack-0.3.0}/src/newstack/data/stacks/go-cli/template/.gitignore +0 -0
- {newstack-0.2.0 → newstack-0.3.0}/src/newstack/data/stacks/go-cli/template/LICENSE +0 -0
- {newstack-0.2.0 → newstack-0.3.0}/src/newstack/data/stacks/go-cli/template/README.md +0 -0
- {newstack-0.2.0 → newstack-0.3.0}/src/newstack/data/stacks/go-cli/template/go.mod +0 -0
- {newstack-0.2.0 → newstack-0.3.0}/src/newstack/data/stacks/go-cli/template/internal/app/app.go +0 -0
- {newstack-0.2.0 → newstack-0.3.0}/src/newstack/data/stacks/go-cli/template/internal/app/app_test.go +0 -0
- {newstack-0.2.0 → newstack-0.3.0}/src/newstack/data/stacks/go-cli/template/main.go +0 -0
- {newstack-0.2.0 → newstack-0.3.0}/src/newstack/data/stacks/node-express-ts/template/.github/workflows/ci.yml +0 -0
- {newstack-0.2.0 → newstack-0.3.0}/src/newstack/data/stacks/node-express-ts/template/.gitignore +0 -0
- {newstack-0.2.0 → newstack-0.3.0}/src/newstack/data/stacks/node-express-ts/template/LICENSE +0 -0
- {newstack-0.2.0 → newstack-0.3.0}/src/newstack/data/stacks/node-express-ts/template/README.md +0 -0
- {newstack-0.2.0 → newstack-0.3.0}/src/newstack/data/stacks/node-express-ts/template/package.json +0 -0
- {newstack-0.2.0 → newstack-0.3.0}/src/newstack/data/stacks/node-express-ts/template/src/app.ts +0 -0
- {newstack-0.2.0 → newstack-0.3.0}/src/newstack/data/stacks/node-express-ts/template/src/server.ts +0 -0
- {newstack-0.2.0 → newstack-0.3.0}/src/newstack/data/stacks/node-express-ts/template/tests/app.test.ts +0 -0
- {newstack-0.2.0 → newstack-0.3.0}/src/newstack/data/stacks/node-express-ts/template/tsconfig.json +0 -0
- {newstack-0.2.0 → newstack-0.3.0}/src/newstack/data/stacks/python-cli/template/.github/workflows/ci.yml +0 -0
- {newstack-0.2.0 → newstack-0.3.0}/src/newstack/data/stacks/python-cli/template/.gitignore +0 -0
- {newstack-0.2.0 → newstack-0.3.0}/src/newstack/data/stacks/python-cli/template/LICENSE +0 -0
- {newstack-0.2.0 → newstack-0.3.0}/src/newstack/data/stacks/python-cli/template/README.md +0 -0
- {newstack-0.2.0 → newstack-0.3.0}/src/newstack/data/stacks/python-cli/template/pyproject.toml +0 -0
- {newstack-0.2.0 → newstack-0.3.0}/src/newstack/data/stacks/python-cli/template/src/{{package_name}}/__init__.py +0 -0
- {newstack-0.2.0 → newstack-0.3.0}/src/newstack/data/stacks/python-cli/template/src/{{package_name}}/__main__.py +0 -0
- {newstack-0.2.0 → newstack-0.3.0}/src/newstack/data/stacks/python-cli/template/src/{{package_name}}/cli.py +0 -0
- {newstack-0.2.0 → newstack-0.3.0}/src/newstack/data/stacks/python-cli/template/tests/test_cli.py +0 -0
- {newstack-0.2.0 → newstack-0.3.0}/src/newstack/data/stacks/python-lib/template/.github/workflows/ci.yml +0 -0
- {newstack-0.2.0 → newstack-0.3.0}/src/newstack/data/stacks/python-lib/template/.gitignore +0 -0
- {newstack-0.2.0 → newstack-0.3.0}/src/newstack/data/stacks/python-lib/template/LICENSE +0 -0
- {newstack-0.2.0 → newstack-0.3.0}/src/newstack/data/stacks/python-lib/template/README.md +0 -0
- {newstack-0.2.0 → newstack-0.3.0}/src/newstack/data/stacks/python-lib/template/pyproject.toml +0 -0
- {newstack-0.2.0 → newstack-0.3.0}/src/newstack/data/stacks/python-lib/template/src/{{package_name}}/__init__.py +0 -0
- {newstack-0.2.0 → newstack-0.3.0}/src/newstack/data/stacks/python-lib/template/src/{{package_name}}/core.py +0 -0
- {newstack-0.2.0 → newstack-0.3.0}/src/newstack/data/stacks/python-lib/template/tests/test_core.py +0 -0
- {newstack-0.2.0 → newstack-0.3.0}/src/newstack/data/stacks/react-vite-ts/template/.github/workflows/ci.yml +0 -0
- {newstack-0.2.0 → newstack-0.3.0}/src/newstack/data/stacks/react-vite-ts/template/.gitignore +0 -0
- {newstack-0.2.0 → newstack-0.3.0}/src/newstack/data/stacks/react-vite-ts/template/LICENSE +0 -0
- {newstack-0.2.0 → newstack-0.3.0}/src/newstack/data/stacks/react-vite-ts/template/README.md +0 -0
- {newstack-0.2.0 → newstack-0.3.0}/src/newstack/data/stacks/react-vite-ts/template/index.html +0 -0
- {newstack-0.2.0 → newstack-0.3.0}/src/newstack/data/stacks/react-vite-ts/template/package.json +0 -0
- {newstack-0.2.0 → newstack-0.3.0}/src/newstack/data/stacks/react-vite-ts/template/src/App.test.tsx +0 -0
- {newstack-0.2.0 → newstack-0.3.0}/src/newstack/data/stacks/react-vite-ts/template/src/App.tsx +0 -0
- {newstack-0.2.0 → newstack-0.3.0}/src/newstack/data/stacks/react-vite-ts/template/src/main.tsx +0 -0
- {newstack-0.2.0 → newstack-0.3.0}/src/newstack/data/stacks/react-vite-ts/template/src/styles.css +0 -0
- {newstack-0.2.0 → newstack-0.3.0}/src/newstack/data/stacks/react-vite-ts/template/tsconfig.json +0 -0
- {newstack-0.2.0 → newstack-0.3.0}/src/newstack/data/stacks/react-vite-ts/template/vite.config.ts +0 -0
- {newstack-0.2.0 → newstack-0.3.0}/src/newstack/data/stacks/rust-cli/template/.github/workflows/ci.yml +0 -0
- {newstack-0.2.0 → newstack-0.3.0}/src/newstack/data/stacks/rust-cli/template/.gitignore +0 -0
- {newstack-0.2.0 → newstack-0.3.0}/src/newstack/data/stacks/rust-cli/template/Cargo.toml +0 -0
- {newstack-0.2.0 → newstack-0.3.0}/src/newstack/data/stacks/rust-cli/template/LICENSE +0 -0
- {newstack-0.2.0 → newstack-0.3.0}/src/newstack/data/stacks/rust-cli/template/README.md +0 -0
- {newstack-0.2.0 → newstack-0.3.0}/src/newstack/data/stacks/rust-cli/template/src/lib.rs +0 -0
- {newstack-0.2.0 → newstack-0.3.0}/src/newstack/data/stacks/rust-cli/template/src/main.rs +0 -0
- {newstack-0.2.0 → newstack-0.3.0}/src/newstack/data/stacks/rust-cli/template/tests/cli.rs +0 -0
- {newstack-0.2.0 → newstack-0.3.0}/src/newstack/data/stacks/static-site/template/.github/workflows/deploy-pages.yml +0 -0
- {newstack-0.2.0 → newstack-0.3.0}/src/newstack/data/stacks/static-site/template/.gitignore +0 -0
- {newstack-0.2.0 → newstack-0.3.0}/src/newstack/data/stacks/static-site/template/LICENSE +0 -0
- {newstack-0.2.0 → newstack-0.3.0}/src/newstack/data/stacks/static-site/template/README.md +0 -0
- {newstack-0.2.0 → newstack-0.3.0}/src/newstack/data/stacks/static-site/template/index.html +0 -0
- {newstack-0.2.0 → newstack-0.3.0}/src/newstack/data/stacks/static-site/template/main.js +0 -0
- {newstack-0.2.0 → newstack-0.3.0}/src/newstack/data/stacks/static-site/template/styles.css +0 -0
- {newstack-0.2.0 → newstack-0.3.0}/src/newstack/data/stacks/static-site/template/tests/site.test.mjs +0 -0
- {newstack-0.2.0 → newstack-0.3.0}/src/newstack/data/stacks/svelte-kit-ts/template/.github/workflows/ci.yml +0 -0
- {newstack-0.2.0 → newstack-0.3.0}/src/newstack/data/stacks/svelte-kit-ts/template/.gitignore +0 -0
- {newstack-0.2.0 → newstack-0.3.0}/src/newstack/data/stacks/svelte-kit-ts/template/.npmrc +0 -0
- {newstack-0.2.0 → newstack-0.3.0}/src/newstack/data/stacks/svelte-kit-ts/template/LICENSE +0 -0
- {newstack-0.2.0 → newstack-0.3.0}/src/newstack/data/stacks/svelte-kit-ts/template/README.md +0 -0
- {newstack-0.2.0 → newstack-0.3.0}/src/newstack/data/stacks/svelte-kit-ts/template/package.json +0 -0
- {newstack-0.2.0 → newstack-0.3.0}/src/newstack/data/stacks/svelte-kit-ts/template/src/app.d.ts +0 -0
- {newstack-0.2.0 → newstack-0.3.0}/src/newstack/data/stacks/svelte-kit-ts/template/src/app.html +0 -0
- {newstack-0.2.0 → newstack-0.3.0}/src/newstack/data/stacks/svelte-kit-ts/template/src/lib/Counter.svelte +0 -0
- {newstack-0.2.0 → newstack-0.3.0}/src/newstack/data/stacks/svelte-kit-ts/template/src/lib/Counter.test.ts +0 -0
- {newstack-0.2.0 → newstack-0.3.0}/src/newstack/data/stacks/svelte-kit-ts/template/src/lib/assets/favicon.svg +0 -0
- {newstack-0.2.0 → newstack-0.3.0}/src/newstack/data/stacks/svelte-kit-ts/template/src/lib/index.ts +0 -0
- {newstack-0.2.0 → newstack-0.3.0}/src/newstack/data/stacks/svelte-kit-ts/template/src/routes/+layout.svelte +0 -0
- {newstack-0.2.0 → newstack-0.3.0}/src/newstack/data/stacks/svelte-kit-ts/template/src/routes/+layout.ts +0 -0
- {newstack-0.2.0 → newstack-0.3.0}/src/newstack/data/stacks/svelte-kit-ts/template/src/routes/+page.svelte +0 -0
- {newstack-0.2.0 → newstack-0.3.0}/src/newstack/data/stacks/svelte-kit-ts/template/src/vitest-setup.ts +0 -0
- {newstack-0.2.0 → newstack-0.3.0}/src/newstack/data/stacks/svelte-kit-ts/template/static/robots.txt +0 -0
- {newstack-0.2.0 → newstack-0.3.0}/src/newstack/data/stacks/svelte-kit-ts/template/tsconfig.json +0 -0
- {newstack-0.2.0 → newstack-0.3.0}/src/newstack/data/stacks/svelte-kit-ts/template/vite.config.ts +0 -0
- {newstack-0.2.0 → newstack-0.3.0}/src/newstack/verify.py +0 -0
- {newstack-0.2.0 → newstack-0.3.0}/tests/test_cli.py +0 -0
- {newstack-0.2.0 → newstack-0.3.0}/tests/test_docs.py +0 -0
- {newstack-0.2.0 → newstack-0.3.0}/tests/test_registry.py +0 -0
- {newstack-0.2.0 → newstack-0.3.0}/tests/test_render.py +0 -0
- {newstack-0.2.0 → newstack-0.3.0}/tests/test_stacks.py +0 -0
|
@@ -6,6 +6,28 @@ All notable changes to this project are documented here. The format follows
|
|
|
6
6
|
|
|
7
7
|
## [Unreleased]
|
|
8
8
|
|
|
9
|
+
## [0.3.0] - 2026-11-02
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
|
|
13
|
+
- **`newstack adopt <dir>`** — retrofit a stack's infrastructure onto a project that already
|
|
14
|
+
has code. Additive by construction: an existing file is never opened for writing, and any
|
|
15
|
+
change that would need a merge inside a file is reported as a manual step instead.
|
|
16
|
+
- Auto-detects the stack from the project's own markers (`go.mod`, `Cargo.toml`,
|
|
17
|
+
`manage.py`, `pyproject.toml`/`package.json` contents, a bare `index.html`); ambiguous
|
|
18
|
+
matches print candidates and exit 2; `--stack` overrides.
|
|
19
|
+
- Copies infrastructure only — workflows, `.gitignore`, lint/type config — never a
|
|
20
|
+
README, LICENSE, `pyproject.toml`, `package.json`, `go.mod` or source file.
|
|
21
|
+
- `--dry-run` reports without writing; re-running is a no-op; `--json` for scripting.
|
|
22
|
+
- `--with-tests` (experimental) also copies the stack's starter tests.
|
|
23
|
+
- Manifest keys `detect`, `detect_fallback`, `adopt` and `adopt_tests`, validated by the
|
|
24
|
+
registry and by tests that require every adoptable path to exist and to be infrastructure.
|
|
25
|
+
|
|
26
|
+
### Changed
|
|
27
|
+
|
|
28
|
+
- `newstack new` and `adopt` share one step formatter, so an unknown `{placeholder}` in a
|
|
29
|
+
manifest's `next_steps` prints literally instead of raising `KeyError`.
|
|
30
|
+
|
|
9
31
|
## [0.2.0] - 2026-11-02
|
|
10
32
|
|
|
11
33
|
### Added
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: newstack
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.0
|
|
4
4
|
Summary: Scaffold a new project on any popular stack — every generated starter is proven green in CI.
|
|
5
5
|
Project-URL: Homepage, https://github.com/DawnofGenX/newstack
|
|
6
6
|
Project-URL: Repository, https://github.com/DawnofGenX/newstack
|
|
@@ -83,6 +83,7 @@ newstack new react-vite-ts ~/code/app
|
|
|
83
83
|
newstack new fastapi ~/code/api -d "Billing service" --author "Your Name"
|
|
84
84
|
newstack new python-lib ~/code/lib --dry-run # preview, write nothing
|
|
85
85
|
newstack new python-cli ~/code/tool --github # create the repo and push it too
|
|
86
|
+
newstack adopt ~/code/old-project --dry-run # retrofit CI/lint onto existing code
|
|
86
87
|
newstack check --all # prove every stack on this machine
|
|
87
88
|
newstack doctor # which toolchains do I have?
|
|
88
89
|
```
|
|
@@ -111,6 +112,35 @@ directory to CI-ready project.
|
|
|
111
112
|
Each generated project ships its own `.github/workflows/` that is ready to run, plus a
|
|
112
113
|
README, an MIT licence and a `.gitignore`.
|
|
113
114
|
|
|
115
|
+
## Adopting an existing project
|
|
116
|
+
|
|
117
|
+
Most projects do not start from a scaffold. `adopt` retrofits a stack's *infrastructure*
|
|
118
|
+
onto a directory that already has code — and it is additive by construction: **an existing
|
|
119
|
+
file is never opened for writing.**
|
|
120
|
+
|
|
121
|
+
```console
|
|
122
|
+
$ newstack adopt ~/sites/analytics-dashboard
|
|
123
|
+
newstack 0.3.0 · adopt → /home/you/sites/analytics-dashboard
|
|
124
|
+
|
|
125
|
+
stack static-site (auto-detected)
|
|
126
|
+
added 2 files
|
|
127
|
+
+ .github/workflows/deploy-pages.yml
|
|
128
|
+
+ .gitignore
|
|
129
|
+
tests not copied — rerun with --with-tests if you want starter tests
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
- **Detection** reads the project's own markers (`go.mod`, `Cargo.toml`, `manage.py`, the
|
|
133
|
+
dependencies in `pyproject.toml` / `package.json`, a bare `index.html`). If two stacks
|
|
134
|
+
match, it prints the candidates and exits 2 rather than guessing; `--stack` overrides.
|
|
135
|
+
- **Scope**: workflows, `.gitignore`, and per-stack lint/type config only. It will not copy
|
|
136
|
+
a README, a LICENSE, a `pyproject.toml`, or source files — those carry your project's
|
|
137
|
+
identity, not infrastructure.
|
|
138
|
+
- **Merges are reported, not attempted.** If your `pyproject.toml` needs the stack's
|
|
139
|
+
`[tool.ruff]` block, adopt says so and leaves the file alone.
|
|
140
|
+
- `--dry-run` prints the same report without writing. Re-running is a no-op.
|
|
141
|
+
- `--with-tests` additionally copies the stack's *starter* tests — experimental, because
|
|
142
|
+
those tests import the sample module and usually need adapting to your code.
|
|
143
|
+
|
|
114
144
|
## The green guarantee
|
|
115
145
|
|
|
116
146
|
A template is only as good as its last successful run, so `newstack` treats
|
|
@@ -154,7 +184,10 @@ without maintaining one template per stack.
|
|
|
154
184
|
|
|
155
185
|
## Adding a stack
|
|
156
186
|
|
|
157
|
-
A stack is a directory with a manifest and a template tree
|
|
187
|
+
A stack is a directory with a manifest and a template tree. The manifest carries four
|
|
188
|
+
optional keys beyond the basics — `detect` (how a directory is recognised as this stack),
|
|
189
|
+
`detect_fallback` (a generic stack that must lose to a specific match), and the `adopt` /
|
|
190
|
+
`adopt_tests` allow-lists used by `newstack adopt`:
|
|
158
191
|
|
|
159
192
|
```text
|
|
160
193
|
src/newstack/data/stacks/my-stack/
|
|
@@ -176,10 +209,16 @@ src/newstack/data/stacks/my-stack/
|
|
|
176
209
|
"next_steps": ["cd {path}", "uv run pytest"],
|
|
177
210
|
"verify": [
|
|
178
211
|
{"name": "test", "cmd": "uv run pytest", "tools": ["uv"], "timeout": 600}
|
|
179
|
-
]
|
|
212
|
+
],
|
|
213
|
+
"detect": [{"file": "pyproject.toml", "contains": "my-framework"}],
|
|
214
|
+
"adopt": [".github/workflows/ci.yml", ".gitignore"],
|
|
215
|
+
"adopt_tests": ["tests/test_smoke.py"]
|
|
180
216
|
}
|
|
181
217
|
```
|
|
182
218
|
|
|
219
|
+
`adopt` entries must exist in the template and must never be an identity file
|
|
220
|
+
(`README.md`, `LICENSE`, `pyproject.toml`, `package.json`, `go.mod`, `Cargo.toml`) — a
|
|
221
|
+
test enforces both rules.<br>
|
|
183
222
|
`{{!` escapes a literal `{{`, and `${{ ... }}` (GitHub Actions) is left untouched.
|
|
184
223
|
Point `NEWSTACK_STACKS` at a directory of your own stacks to use private ones — same
|
|
185
224
|
manifest format, no fork required.
|
|
@@ -59,6 +59,7 @@ newstack new react-vite-ts ~/code/app
|
|
|
59
59
|
newstack new fastapi ~/code/api -d "Billing service" --author "Your Name"
|
|
60
60
|
newstack new python-lib ~/code/lib --dry-run # preview, write nothing
|
|
61
61
|
newstack new python-cli ~/code/tool --github # create the repo and push it too
|
|
62
|
+
newstack adopt ~/code/old-project --dry-run # retrofit CI/lint onto existing code
|
|
62
63
|
newstack check --all # prove every stack on this machine
|
|
63
64
|
newstack doctor # which toolchains do I have?
|
|
64
65
|
```
|
|
@@ -87,6 +88,35 @@ directory to CI-ready project.
|
|
|
87
88
|
Each generated project ships its own `.github/workflows/` that is ready to run, plus a
|
|
88
89
|
README, an MIT licence and a `.gitignore`.
|
|
89
90
|
|
|
91
|
+
## Adopting an existing project
|
|
92
|
+
|
|
93
|
+
Most projects do not start from a scaffold. `adopt` retrofits a stack's *infrastructure*
|
|
94
|
+
onto a directory that already has code — and it is additive by construction: **an existing
|
|
95
|
+
file is never opened for writing.**
|
|
96
|
+
|
|
97
|
+
```console
|
|
98
|
+
$ newstack adopt ~/sites/analytics-dashboard
|
|
99
|
+
newstack 0.3.0 · adopt → /home/you/sites/analytics-dashboard
|
|
100
|
+
|
|
101
|
+
stack static-site (auto-detected)
|
|
102
|
+
added 2 files
|
|
103
|
+
+ .github/workflows/deploy-pages.yml
|
|
104
|
+
+ .gitignore
|
|
105
|
+
tests not copied — rerun with --with-tests if you want starter tests
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
- **Detection** reads the project's own markers (`go.mod`, `Cargo.toml`, `manage.py`, the
|
|
109
|
+
dependencies in `pyproject.toml` / `package.json`, a bare `index.html`). If two stacks
|
|
110
|
+
match, it prints the candidates and exits 2 rather than guessing; `--stack` overrides.
|
|
111
|
+
- **Scope**: workflows, `.gitignore`, and per-stack lint/type config only. It will not copy
|
|
112
|
+
a README, a LICENSE, a `pyproject.toml`, or source files — those carry your project's
|
|
113
|
+
identity, not infrastructure.
|
|
114
|
+
- **Merges are reported, not attempted.** If your `pyproject.toml` needs the stack's
|
|
115
|
+
`[tool.ruff]` block, adopt says so and leaves the file alone.
|
|
116
|
+
- `--dry-run` prints the same report without writing. Re-running is a no-op.
|
|
117
|
+
- `--with-tests` additionally copies the stack's *starter* tests — experimental, because
|
|
118
|
+
those tests import the sample module and usually need adapting to your code.
|
|
119
|
+
|
|
90
120
|
## The green guarantee
|
|
91
121
|
|
|
92
122
|
A template is only as good as its last successful run, so `newstack` treats
|
|
@@ -130,7 +160,10 @@ without maintaining one template per stack.
|
|
|
130
160
|
|
|
131
161
|
## Adding a stack
|
|
132
162
|
|
|
133
|
-
A stack is a directory with a manifest and a template tree
|
|
163
|
+
A stack is a directory with a manifest and a template tree. The manifest carries four
|
|
164
|
+
optional keys beyond the basics — `detect` (how a directory is recognised as this stack),
|
|
165
|
+
`detect_fallback` (a generic stack that must lose to a specific match), and the `adopt` /
|
|
166
|
+
`adopt_tests` allow-lists used by `newstack adopt`:
|
|
134
167
|
|
|
135
168
|
```text
|
|
136
169
|
src/newstack/data/stacks/my-stack/
|
|
@@ -152,10 +185,16 @@ src/newstack/data/stacks/my-stack/
|
|
|
152
185
|
"next_steps": ["cd {path}", "uv run pytest"],
|
|
153
186
|
"verify": [
|
|
154
187
|
{"name": "test", "cmd": "uv run pytest", "tools": ["uv"], "timeout": 600}
|
|
155
|
-
]
|
|
188
|
+
],
|
|
189
|
+
"detect": [{"file": "pyproject.toml", "contains": "my-framework"}],
|
|
190
|
+
"adopt": [".github/workflows/ci.yml", ".gitignore"],
|
|
191
|
+
"adopt_tests": ["tests/test_smoke.py"]
|
|
156
192
|
}
|
|
157
193
|
```
|
|
158
194
|
|
|
195
|
+
`adopt` entries must exist in the template and must never be an identity file
|
|
196
|
+
(`README.md`, `LICENSE`, `pyproject.toml`, `package.json`, `go.mod`, `Cargo.toml`) — a
|
|
197
|
+
test enforces both rules.<br>
|
|
159
198
|
`{{!` escapes a literal `{{`, and `${{ ... }}` (GitHub Actions) is left untouched.
|
|
160
199
|
Point `NEWSTACK_STACKS` at a directory of your own stacks to use private ones — same
|
|
161
200
|
manifest format, no fork required.
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "newstack"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.3.0"
|
|
8
8
|
description = "Scaffold a new project on any popular stack — every generated starter is proven green in CI."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.10"
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
"""Adopt an existing project: copy a stack's infrastructure, additively.
|
|
2
|
+
|
|
3
|
+
The rule that makes this safe: an existing file is never opened for writing. Anything
|
|
4
|
+
already present is reported as "kept", and anything that would need a merge inside a
|
|
5
|
+
file (a ruff block in someone's pyproject, a test script in their package.json) is
|
|
6
|
+
reported as a manual step instead of being attempted.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
from dataclasses import dataclass, field
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
|
|
14
|
+
from .registry import Stack, load_stacks
|
|
15
|
+
from .render import TemplateError, render_tree
|
|
16
|
+
|
|
17
|
+
# Files adopt must never copy into someone else's project: they carry identity
|
|
18
|
+
# (licence, name, entrypoints) rather than infrastructure.
|
|
19
|
+
FORBIDDEN = frozenset(
|
|
20
|
+
{
|
|
21
|
+
"README.md",
|
|
22
|
+
"README.rst",
|
|
23
|
+
"LICENSE",
|
|
24
|
+
"LICENSE.md",
|
|
25
|
+
"pyproject.toml",
|
|
26
|
+
"package.json",
|
|
27
|
+
"go.mod",
|
|
28
|
+
"Cargo.toml",
|
|
29
|
+
}
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
@dataclass
|
|
34
|
+
class PendingFile:
|
|
35
|
+
rel: str
|
|
36
|
+
payload: bytes
|
|
37
|
+
executable: bool = False
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
@dataclass
|
|
41
|
+
class AdoptionPlan:
|
|
42
|
+
stack: Stack
|
|
43
|
+
root: Path
|
|
44
|
+
detected: bool = True
|
|
45
|
+
add: list[PendingFile] = field(default_factory=list)
|
|
46
|
+
keep: list[str] = field(default_factory=list)
|
|
47
|
+
manual: list[str] = field(default_factory=list)
|
|
48
|
+
tests_available: bool = False
|
|
49
|
+
|
|
50
|
+
@property
|
|
51
|
+
def added(self) -> list[str]:
|
|
52
|
+
return [item.rel for item in self.add]
|
|
53
|
+
|
|
54
|
+
def as_dict(self) -> dict:
|
|
55
|
+
return {
|
|
56
|
+
"stack": self.stack.name,
|
|
57
|
+
"root": str(self.root),
|
|
58
|
+
"detected": self.detected,
|
|
59
|
+
"added": self.added,
|
|
60
|
+
"kept": self.keep,
|
|
61
|
+
"manual": self.manual,
|
|
62
|
+
"tests_available": self.tests_available,
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def detect_stacks(root: Path) -> list[Stack]:
|
|
67
|
+
"""Stacks whose markers match *root*; generic fallbacks are dropped on a specific match."""
|
|
68
|
+
stacks = load_stacks()
|
|
69
|
+
matched = [stack for stack in stacks.values() if stack.detect and stack.matches(root)]
|
|
70
|
+
specific = [stack for stack in matched if not stack.detect_fallback]
|
|
71
|
+
fallbacks = [stack for stack in matched if stack.detect_fallback]
|
|
72
|
+
return specific or fallbacks
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def _manual_steps(stack: Stack, root: Path) -> list[str]:
|
|
76
|
+
steps: list[str] = []
|
|
77
|
+
if stack.language == "python":
|
|
78
|
+
target = root / "pyproject.toml"
|
|
79
|
+
if not target.is_file():
|
|
80
|
+
steps.append("no pyproject.toml found: `newstack new` is a better fit than adopt")
|
|
81
|
+
else:
|
|
82
|
+
steps.append(
|
|
83
|
+
"merge the stack's [tool.ruff] / [tool.pytest.ini_options] blocks into "
|
|
84
|
+
"pyproject.toml by hand (adopt never edits existing files)"
|
|
85
|
+
)
|
|
86
|
+
elif stack.language in {"node", "html"}:
|
|
87
|
+
target = root / "package.json"
|
|
88
|
+
if target.is_file():
|
|
89
|
+
steps.append(
|
|
90
|
+
"add the stack's lint/test scripts to package.json by hand "
|
|
91
|
+
"(adopt never edits existing files)"
|
|
92
|
+
)
|
|
93
|
+
elif stack.language == "go":
|
|
94
|
+
if not (root / ".gitignore").exists():
|
|
95
|
+
steps.append("consider adding bin/ to your .gitignore for build output")
|
|
96
|
+
elif stack.language == "rust":
|
|
97
|
+
steps.append("Cargo.lock should be committed for binaries; .gitignore keeps /target out")
|
|
98
|
+
return steps
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def plan_adoption(
|
|
102
|
+
stack: Stack,
|
|
103
|
+
root: Path,
|
|
104
|
+
context: dict,
|
|
105
|
+
*,
|
|
106
|
+
with_tests: bool = False,
|
|
107
|
+
detected: bool = True,
|
|
108
|
+
) -> AdoptionPlan:
|
|
109
|
+
"""Work out what adopt would do, without touching the filesystem."""
|
|
110
|
+
if not stack.adopt:
|
|
111
|
+
raise TemplateError(f"stack '{stack.name}' declares no adoptable files")
|
|
112
|
+
|
|
113
|
+
wanted = list(stack.adopt)
|
|
114
|
+
if with_tests:
|
|
115
|
+
wanted.extend(stack.adopt_tests)
|
|
116
|
+
|
|
117
|
+
forbidden = sorted(set(wanted) & FORBIDDEN)
|
|
118
|
+
if forbidden:
|
|
119
|
+
raise TemplateError(
|
|
120
|
+
f"stack '{stack.name}' tries to adopt files that carry project identity: "
|
|
121
|
+
f"{', '.join(forbidden)}"
|
|
122
|
+
)
|
|
123
|
+
|
|
124
|
+
entries, _dirs = render_tree(stack.template_dir, context)
|
|
125
|
+
by_path = {rel: (payload, executable) for rel, payload, executable in entries}
|
|
126
|
+
|
|
127
|
+
missing = [rel for rel in wanted if rel not in by_path]
|
|
128
|
+
if missing:
|
|
129
|
+
raise TemplateError(
|
|
130
|
+
f"stack '{stack.name}' lists adoptable files that are not in its template: "
|
|
131
|
+
f"{', '.join(missing)}"
|
|
132
|
+
)
|
|
133
|
+
|
|
134
|
+
plan = AdoptionPlan(stack=stack, root=root, detected=detected)
|
|
135
|
+
plan.tests_available = bool(stack.adopt_tests) and with_tests
|
|
136
|
+
for rel in wanted:
|
|
137
|
+
if (root / rel).exists():
|
|
138
|
+
plan.keep.append(rel)
|
|
139
|
+
continue
|
|
140
|
+
payload, executable = by_path[rel]
|
|
141
|
+
plan.add.append(PendingFile(rel=rel, payload=payload, executable=executable))
|
|
142
|
+
|
|
143
|
+
plan.manual = _manual_steps(stack, root)
|
|
144
|
+
return plan
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
def apply_plan(plan: AdoptionPlan) -> list[str]:
|
|
148
|
+
"""Write the planned files. Only touches paths that did not exist when planned."""
|
|
149
|
+
written: list[str] = []
|
|
150
|
+
for item in plan.add:
|
|
151
|
+
target = plan.root / item.rel
|
|
152
|
+
if target.exists(): # someone else created it since planning: leave it alone
|
|
153
|
+
plan.keep.append(item.rel)
|
|
154
|
+
continue
|
|
155
|
+
target.parent.mkdir(parents=True, exist_ok=True)
|
|
156
|
+
target.write_bytes(item.payload)
|
|
157
|
+
if item.executable:
|
|
158
|
+
target.chmod(0o755)
|
|
159
|
+
written.append(item.rel)
|
|
160
|
+
return written
|
|
@@ -12,6 +12,7 @@ from datetime import datetime, timezone
|
|
|
12
12
|
from pathlib import Path
|
|
13
13
|
|
|
14
14
|
from . import __version__
|
|
15
|
+
from .adopt import apply_plan, detect_stacks, plan_adoption
|
|
15
16
|
from .registry import RegistryError, Stack, get_stack, load_stacks
|
|
16
17
|
from .render import TemplateError, generate
|
|
17
18
|
from .verify import check_stack, missing_tools
|
|
@@ -72,6 +73,24 @@ def _git_config(key: str) -> str:
|
|
|
72
73
|
return value
|
|
73
74
|
|
|
74
75
|
|
|
76
|
+
class _SafeDict(dict):
|
|
77
|
+
"""format_map target that leaves unknown keys visible instead of raising."""
|
|
78
|
+
|
|
79
|
+
def __missing__(self, key: str) -> str:
|
|
80
|
+
return "{" + key + "}"
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def _format_step(step: str, context: dict, path: Path) -> str:
|
|
84
|
+
return step.format_map(
|
|
85
|
+
_SafeDict(
|
|
86
|
+
path=path,
|
|
87
|
+
project_name=context["project_name"],
|
|
88
|
+
package_name=context["package_name"],
|
|
89
|
+
npm_name=context["npm_name"],
|
|
90
|
+
)
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
|
|
75
94
|
def build_context(stack: Stack, destination: Path, args: argparse.Namespace) -> dict:
|
|
76
95
|
project_name = args.name or destination.name
|
|
77
96
|
author = args.author or _git_config("user.name") or "Your Name"
|
|
@@ -200,18 +219,77 @@ def _print_new_report(
|
|
|
200
219
|
print(f" {'github':<13} {report['github']}")
|
|
201
220
|
|
|
202
221
|
if stack.next_steps:
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
222
|
+
print("\n next:")
|
|
223
|
+
for step in stack.next_steps:
|
|
224
|
+
print(f" {_format_step(step, context, destination)}")
|
|
225
|
+
print()
|
|
226
|
+
|
|
227
|
+
|
|
228
|
+
def cmd_adopt(args: argparse.Namespace) -> int:
|
|
229
|
+
root = Path(args.path).expanduser().resolve()
|
|
230
|
+
if not root.is_dir():
|
|
231
|
+
raise TemplateError(f"not a directory: {root}")
|
|
232
|
+
|
|
233
|
+
if args.stack:
|
|
234
|
+
stack = get_stack(args.stack)
|
|
235
|
+
detected = False
|
|
236
|
+
else:
|
|
237
|
+
candidates = detect_stacks(root)
|
|
238
|
+
if not candidates:
|
|
239
|
+
raise TemplateError(
|
|
240
|
+
"could not recognise this project's stack — pass --stack "
|
|
241
|
+
"(see `newstack list`)"
|
|
209
242
|
)
|
|
210
|
-
|
|
211
|
-
|
|
243
|
+
if len(candidates) > 1:
|
|
244
|
+
names = ", ".join(candidate.name for candidate in candidates)
|
|
245
|
+
print(f"newstack: this directory matches more than one stack: {names}", file=sys.stderr)
|
|
246
|
+
print("newstack: pass --stack <name> to choose one", file=sys.stderr)
|
|
247
|
+
return 2
|
|
248
|
+
stack = candidates[0]
|
|
249
|
+
detected = True
|
|
250
|
+
|
|
251
|
+
context = build_context(stack, root, args)
|
|
252
|
+
plan = plan_adoption(
|
|
253
|
+
stack, root, context, with_tests=args.with_tests, detected=detected
|
|
254
|
+
)
|
|
255
|
+
|
|
256
|
+
if not args.dry_run:
|
|
257
|
+
apply_plan(plan)
|
|
258
|
+
|
|
259
|
+
if args.json:
|
|
260
|
+
payload = plan.as_dict()
|
|
261
|
+
payload["dry_run"] = args.dry_run
|
|
262
|
+
print(json.dumps(payload, indent=2))
|
|
263
|
+
return 0
|
|
264
|
+
|
|
265
|
+
_print_adopt_report(plan, context, dry_run=args.dry_run)
|
|
266
|
+
return 0
|
|
267
|
+
|
|
268
|
+
|
|
269
|
+
def _print_adopt_report(plan, context: dict, *, dry_run: bool) -> None:
|
|
270
|
+
print(f"\nnewstack {__version__} · adopt → {plan.root}\n")
|
|
271
|
+
how = "auto-detected" if plan.detected else "--stack"
|
|
272
|
+
print(f" {'stack':<12} {plan.stack.name} ({how})")
|
|
273
|
+
verb = "would add" if dry_run else "added"
|
|
274
|
+
print(f" {verb:<12} {len(plan.add)} files")
|
|
275
|
+
for item in plan.add:
|
|
276
|
+
print(f" + {item.rel}")
|
|
277
|
+
if plan.keep:
|
|
278
|
+
print(f" {'kept':<12} {len(plan.keep)} existing files, left untouched")
|
|
279
|
+
for rel in plan.keep:
|
|
280
|
+
print(f" = {rel}")
|
|
281
|
+
if plan.manual:
|
|
282
|
+
print(f" {'manual':<12} {plan.manual[0]}")
|
|
283
|
+
for extra in plan.manual[1:]:
|
|
284
|
+
print(f" {'':<12} {extra}")
|
|
285
|
+
if not plan.add:
|
|
286
|
+
print(f" {'nothing':<12} the infrastructure files are already present")
|
|
287
|
+
if not plan.tests_available and plan.stack.adopt_tests:
|
|
288
|
+
print(f" {'tests':<12} not copied — rerun with --with-tests if you want starter tests")
|
|
289
|
+
if plan.stack.next_steps:
|
|
212
290
|
print("\n next:")
|
|
213
|
-
|
|
214
|
-
|
|
291
|
+
print(f" review the additions: git -C {plan.root} status --short")
|
|
292
|
+
print(" checks run on push: the workflow above is the only thing that changed")
|
|
215
293
|
print()
|
|
216
294
|
|
|
217
295
|
|
|
@@ -349,6 +427,20 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
349
427
|
new.add_argument("--json", action="store_true", help="machine readable output")
|
|
350
428
|
new.set_defaults(handler=cmd_new)
|
|
351
429
|
|
|
430
|
+
adopt = sub.add_parser(
|
|
431
|
+
"adopt", help="retrofit a stack's CI/lint files onto an existing project"
|
|
432
|
+
)
|
|
433
|
+
adopt.add_argument("path", help="directory of the existing project")
|
|
434
|
+
adopt.add_argument("--stack", help="force a stack instead of auto-detecting")
|
|
435
|
+
adopt.add_argument(
|
|
436
|
+
"--with-tests", action="store_true", help="also copy the stack's starter tests"
|
|
437
|
+
)
|
|
438
|
+
adopt.add_argument("--dry-run", action="store_true", help="report without writing anything")
|
|
439
|
+
adopt.add_argument("--author", help="author name for rendered files")
|
|
440
|
+
adopt.add_argument("--email", help="author email for rendered files")
|
|
441
|
+
adopt.add_argument("--json", action="store_true", help="machine readable output")
|
|
442
|
+
adopt.set_defaults(handler=cmd_adopt, name=None, description=None)
|
|
443
|
+
|
|
352
444
|
lst = sub.add_parser("list", help="list available stacks")
|
|
353
445
|
lst.add_argument("--json", action="store_true")
|
|
354
446
|
lst.set_defaults(handler=cmd_list)
|
|
@@ -55,5 +55,23 @@
|
|
|
55
55
|
],
|
|
56
56
|
"timeout": 600
|
|
57
57
|
}
|
|
58
|
+
],
|
|
59
|
+
"detect": [
|
|
60
|
+
{
|
|
61
|
+
"file": "manage.py"
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
"file": "pyproject.toml",
|
|
65
|
+
"contains": "django"
|
|
66
|
+
}
|
|
67
|
+
],
|
|
68
|
+
"detect_fallback": false,
|
|
69
|
+
"adopt": [
|
|
70
|
+
".github/workflows/ci.yml",
|
|
71
|
+
".gitignore"
|
|
72
|
+
],
|
|
73
|
+
"adopt_tests": [
|
|
74
|
+
"tests/test_views.py",
|
|
75
|
+
"tests/test_models.py"
|
|
58
76
|
]
|
|
59
77
|
}
|
|
@@ -38,5 +38,20 @@
|
|
|
38
38
|
],
|
|
39
39
|
"timeout": 300
|
|
40
40
|
}
|
|
41
|
+
],
|
|
42
|
+
"detect": [
|
|
43
|
+
{
|
|
44
|
+
"file": "pyproject.toml",
|
|
45
|
+
"contains": "fastapi"
|
|
46
|
+
}
|
|
47
|
+
],
|
|
48
|
+
"detect_fallback": false,
|
|
49
|
+
"adopt": [
|
|
50
|
+
".github/workflows/ci.yml",
|
|
51
|
+
".gitignore",
|
|
52
|
+
"Dockerfile"
|
|
53
|
+
],
|
|
54
|
+
"adopt_tests": [
|
|
55
|
+
"tests/test_api.py"
|
|
41
56
|
]
|
|
42
57
|
}
|
|
@@ -38,5 +38,18 @@
|
|
|
38
38
|
],
|
|
39
39
|
"timeout": 600
|
|
40
40
|
}
|
|
41
|
+
],
|
|
42
|
+
"detect": [
|
|
43
|
+
{
|
|
44
|
+
"file": "go.mod"
|
|
45
|
+
}
|
|
46
|
+
],
|
|
47
|
+
"detect_fallback": false,
|
|
48
|
+
"adopt": [
|
|
49
|
+
".github/workflows/ci.yml",
|
|
50
|
+
".gitignore"
|
|
51
|
+
],
|
|
52
|
+
"adopt_tests": [
|
|
53
|
+
"internal/app/app_test.go"
|
|
41
54
|
]
|
|
42
55
|
}
|
|
@@ -10,5 +10,20 @@
|
|
|
10
10
|
{"name": "install", "cmd": "npm install --no-audit --no-fund --silent", "tools": ["npm"], "timeout": 900},
|
|
11
11
|
{"name": "typecheck", "cmd": "npm run typecheck", "tools": ["npm"], "timeout": 300},
|
|
12
12
|
{"name": "test", "cmd": "npm test", "tools": ["npm"], "timeout": 300}
|
|
13
|
+
],
|
|
14
|
+
"detect": [
|
|
15
|
+
{
|
|
16
|
+
"file": "package.json",
|
|
17
|
+
"contains": "\"express\""
|
|
18
|
+
}
|
|
19
|
+
],
|
|
20
|
+
"detect_fallback": false,
|
|
21
|
+
"adopt": [
|
|
22
|
+
".github/workflows/ci.yml",
|
|
23
|
+
".gitignore",
|
|
24
|
+
"tsconfig.json"
|
|
25
|
+
],
|
|
26
|
+
"adopt_tests": [
|
|
27
|
+
"tests/app.test.ts"
|
|
13
28
|
]
|
|
14
29
|
}
|
|
@@ -46,5 +46,22 @@
|
|
|
46
46
|
],
|
|
47
47
|
"timeout": 120
|
|
48
48
|
}
|
|
49
|
+
],
|
|
50
|
+
"detect": [
|
|
51
|
+
{
|
|
52
|
+
"file": "pyproject.toml",
|
|
53
|
+
"contains": "[project.scripts]"
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
"file": "src"
|
|
57
|
+
}
|
|
58
|
+
],
|
|
59
|
+
"detect_fallback": false,
|
|
60
|
+
"adopt": [
|
|
61
|
+
".github/workflows/ci.yml",
|
|
62
|
+
".gitignore"
|
|
63
|
+
],
|
|
64
|
+
"adopt_tests": [
|
|
65
|
+
"tests/test_cli.py"
|
|
49
66
|
]
|
|
50
67
|
}
|
|
@@ -38,5 +38,18 @@
|
|
|
38
38
|
],
|
|
39
39
|
"timeout": 300
|
|
40
40
|
}
|
|
41
|
+
],
|
|
42
|
+
"detect": [
|
|
43
|
+
{
|
|
44
|
+
"file": "pyproject.toml"
|
|
45
|
+
}
|
|
46
|
+
],
|
|
47
|
+
"detect_fallback": true,
|
|
48
|
+
"adopt": [
|
|
49
|
+
".github/workflows/ci.yml",
|
|
50
|
+
".gitignore"
|
|
51
|
+
],
|
|
52
|
+
"adopt_tests": [
|
|
53
|
+
"tests/test_core.py"
|
|
41
54
|
]
|
|
42
55
|
}
|
|
@@ -11,5 +11,21 @@
|
|
|
11
11
|
{"name": "typecheck", "cmd": "npm run typecheck", "tools": ["npm"], "timeout": 300},
|
|
12
12
|
{"name": "test", "cmd": "npm test", "tools": ["npm"], "timeout": 300},
|
|
13
13
|
{"name": "build", "cmd": "npm run build", "tools": ["npm"], "timeout": 300}
|
|
14
|
+
],
|
|
15
|
+
"detect": [
|
|
16
|
+
{
|
|
17
|
+
"file": "package.json",
|
|
18
|
+
"contains": "\"react\""
|
|
19
|
+
}
|
|
20
|
+
],
|
|
21
|
+
"detect_fallback": false,
|
|
22
|
+
"adopt": [
|
|
23
|
+
".github/workflows/ci.yml",
|
|
24
|
+
".gitignore",
|
|
25
|
+
"tsconfig.json",
|
|
26
|
+
"vite.config.ts"
|
|
27
|
+
],
|
|
28
|
+
"adopt_tests": [
|
|
29
|
+
"src/App.test.tsx"
|
|
14
30
|
]
|
|
15
31
|
}
|