create-foundry-workspace 1.0.0 → 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +172 -170
- package/dist/detection/detector.d.ts +3 -2
- package/dist/detection/detector.js +9 -7
- package/dist/detection/rules/go.d.ts +2 -1
- package/dist/detection/rules/go.js +2 -4
- package/dist/detection/rules/java.d.ts +2 -1
- package/dist/detection/rules/java.js +6 -23
- package/dist/detection/rules/node.d.ts +2 -1
- package/dist/detection/rules/node.js +3 -8
- package/dist/detection/rules/python.d.ts +2 -1
- package/dist/detection/rules/python.js +6 -23
- package/dist/detection/rules/rust.d.ts +2 -1
- package/dist/detection/rules/rust.js +2 -5
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/io/paths.js +2 -1
- package/dist/io/scanner.d.ts +18 -0
- package/dist/io/scanner.js +26 -0
- package/dist/templates/contributing.js +62 -14
- package/package.json +64 -64
package/README.md
CHANGED
|
@@ -1,170 +1,172 @@
|
|
|
1
|
-
# Foundry (`create-foundry-workspace`)
|
|
2
|
-
|
|
3
|
-
> **Deterministic, local-first, zero-LLM CLI for initializing AI-ready software development workspaces.**
|
|
4
|
-
|
|
5
|
-
[](https://github.com/Harshitkant21/Foundry/actions)
|
|
6
|
-
[](https://www.npmjs.com/package/create-foundry-workspace)
|
|
7
|
-
[](package.json)
|
|
8
|
-
[](LICENSE)
|
|
9
|
-
[](docs/ARCHITECTURE.md)
|
|
10
|
-
|
|
11
|
-
---
|
|
12
|
-
|
|
13
|
-
## What is Foundry?
|
|
14
|
-
|
|
15
|
-
Modern developers increasingly rely on AI coding assistants such as **Cursor**, **Claude Code**, and **GitHub Copilot**. However, when starting a new project, developers are forced to manually author and repeatedly recreate AI instruction files, project rules, context files, and guardrails.
|
|
16
|
-
|
|
17
|
-
**Foundry** is an open-source, deterministic developer tool distributed via npm as **`create-foundry-workspace`**. When starting a new project, running:
|
|
18
|
-
|
|
19
|
-
```bash
|
|
20
|
-
npx create-foundry-workspace
|
|
21
|
-
# or: npm create foundry-workspace
|
|
22
|
-
```
|
|
23
|
-
|
|
24
|
-
asks a minimal set of meaningful project initialization questions and deterministically scaffolds an **AI-ready software workspace** based on predefined, versioned, and maintainable templates.
|
|
25
|
-
|
|
26
|
-
---
|
|
27
|
-
|
|
28
|
-
## What Foundry Does
|
|
29
|
-
|
|
30
|
-
- **Establishes a Canonical Contract:** Generates a clean, machine-readable `ai-workspace.yaml` manifest.
|
|
31
|
-
- **Generates Native Tool Adapters:** Renders native, complete instruction files for **Cursor** (`.cursor/rules/*.mdc`), **Claude Code** (`CLAUDE.md`), **GitHub Copilot** (`.github/copilot-instructions.md`), and the cross-tool open standard **`AGENTS.md`**.
|
|
32
|
-
- **Generates Project README:** Creates a deterministic, human-facing `README.md` tailored to the selected project stack, commands, and AI tools.
|
|
33
|
-
- **Enforces Architectural Guardrails:** Injects strict or standard code quality, security, and testing guardrails.
|
|
34
|
-
- **Safeguards Existing Files:** Supports non-destructive safe-merges, overwrite protection, and dry-run execution.
|
|
35
|
-
|
|
36
|
-
---
|
|
37
|
-
|
|
38
|
-
## What Foundry Explicitly Does NOT Do
|
|
39
|
-
|
|
40
|
-
Foundry is **NOT an AI agent** and does not use AI to generate workspaces:
|
|
41
|
-
- ❌ **NO OpenAI, Anthropic, or Gemini API calls**
|
|
42
|
-
- ❌ **NO local or remote LLM inference**
|
|
43
|
-
- ❌ **NO network requests at runtime** (100% offline and local-first)
|
|
44
|
-
- ❌ **NO hidden API keys or telemetry**
|
|
45
|
-
- ❌ **NO autonomous code generation**
|
|
46
|
-
|
|
47
|
-
Output is 100% deterministic: given the same inputs and version, the generated workspace is bit-for-bit identical every time.
|
|
48
|
-
|
|
49
|
-
---
|
|
50
|
-
|
|
51
|
-
## Quick Start
|
|
52
|
-
|
|
53
|
-
Initialize an AI-ready workspace in your current directory:
|
|
54
|
-
```bash
|
|
55
|
-
npx create-foundry-workspace
|
|
56
|
-
# or: npm create foundry-workspace
|
|
57
|
-
```
|
|
58
|
-
|
|
59
|
-
Or target a specific folder:
|
|
60
|
-
```bash
|
|
61
|
-
npx create-foundry-workspace ./my-new-project
|
|
62
|
-
```
|
|
63
|
-
|
|
64
|
-
### Non-Interactive (CI / Scripted) Usage
|
|
65
|
-
```bash
|
|
66
|
-
npx create-foundry-workspace ./my-new-service \
|
|
67
|
-
--name my-new-service \
|
|
68
|
-
--type api \
|
|
69
|
-
--lang python \
|
|
70
|
-
--strictness strict \
|
|
71
|
-
--ai cursor,claude,copilot,agents \
|
|
72
|
-
--yes
|
|
73
|
-
```
|
|
74
|
-
|
|
75
|
-
---
|
|
76
|
-
|
|
77
|
-
## CLI Options
|
|
78
|
-
|
|
79
|
-
| Flag | Description | Default |
|
|
80
|
-
|---|---|---|
|
|
81
|
-
| `[directory]` | Target directory to initialize | `.` |
|
|
82
|
-
| `-n, --name <string>` | Project name | Inferred from folder or `package.json` |
|
|
83
|
-
| `-t, --type <type>` | `web` \| `api` \| `fullstack` \| `cli` \| `library` \| `data-ml` \| `mobile` \| `custom` | Inferred or `fullstack` |
|
|
84
|
-
| `-l, --lang <lang>` | `typescript` \| `javascript` \| `python` \| `java` \| `go` \| `rust` \| `other` | Inferred or `typescript` |
|
|
85
|
-
| `-r, --repo <repo>` | `github` \| `gitlab` \| `none` | `github` |
|
|
86
|
-
| `-s, --strictness <level>` | `standard` \| `strict` | `standard` |
|
|
87
|
-
| `-a, --ai <adapters>` | Comma-separated list of adapters (`claude,cursor,agents,copilot,codex,gemini,generic`) | `claude,cursor,agents` |
|
|
88
|
-
| `--dry-run` | Preview files in terminal without writing to disk | `false` |
|
|
89
|
-
| `-f, --force` | Overwrite conflicting files | `false` |
|
|
90
|
-
| `-y, --yes` | Accept all defaults non-interactively | `false` |
|
|
91
|
-
| `-v, --version` | Display version | |
|
|
92
|
-
| `-h, --help` | Display help | |
|
|
93
|
-
|
|
94
|
-
---
|
|
95
|
-
|
|
96
|
-
## Generated Workspace Structure
|
|
97
|
-
|
|
98
|
-
Foundry generates a deterministic, standardized workspace with zero bloat and zero hallucinations:
|
|
99
|
-
|
|
100
|
-
```text
|
|
101
|
-
my-project/
|
|
102
|
-
├── ai-workspace.yaml # Canonical machine contract
|
|
103
|
-
├── README.md # Project documentation & stack guide
|
|
104
|
-
├── AI_CONTEXT.md # System map and entrypoint for AI assistants
|
|
105
|
-
├── PROJECT_GUARDRAILS.md # Standalone architectural guardrails & testing rules
|
|
106
|
-
├── CONTRIBUTING.md # Contribution workflow & guidelines
|
|
107
|
-
├── .gitignore # Language-tailored ignore rules
|
|
108
|
-
│
|
|
109
|
-
├── .ai/ # AI operational intelligence directory
|
|
110
|
-
│ ├── context/project-context.md # Detailed project domain background
|
|
111
|
-
│ ├── rules/00-core-rules.md # Core workspace rules & coding conventions
|
|
112
|
-
│ ├── architecture/system-design.md # High-level architecture & layer boundaries
|
|
113
|
-
│ ├── decisions/0001-project-init.md # Initial architecture decision record (ADR)
|
|
114
|
-
│ └── tasks/task-backlog.md # Operational scratchpad for AI agent task execution
|
|
115
|
-
│
|
|
116
|
-
├── docs/ # Human-facing project documentation
|
|
117
|
-
│ ├── README.md # Documentation index
|
|
118
|
-
│ ├── ARCHITECTURE.md # System architecture & component design
|
|
119
|
-
│ ├── DEVELOPMENT.md # Local setup, run, and test guides
|
|
120
|
-
│ └── DECISIONS.md # Architecture decision records index
|
|
121
|
-
│
|
|
122
|
-
└── [Tool Adapters] # Based on user selection:
|
|
123
|
-
├── AGENTS.md # Multi-Agent open standard
|
|
124
|
-
├── CLAUDE.md # Anthropic Claude Code CLI
|
|
125
|
-
├── .cursor/rules/*.mdc # Cursor IDE modular rules
|
|
126
|
-
├── .github/copilot-instructions.md # GitHub Copilot context
|
|
127
|
-
├── .codex/instructions.md # OpenAI Codex context
|
|
128
|
-
├── GEMINI.md # Google Antigravity & Gemini CLI
|
|
129
|
-
└── AI_INSTRUCTIONS.md # Generic AI assistant instructions
|
|
130
|
-
```
|
|
131
|
-
|
|
132
|
-
---
|
|
133
|
-
|
|
134
|
-
## Repository Architecture & Dogfooding
|
|
135
|
-
|
|
136
|
-
Foundry strictly practices **dogfooding**. The Foundry repository itself is governed by the same `ai-workspace.yaml`, `AGENTS.md`, `CLAUDE.md`, `.cursor/`, and `.github/` files that it generates for users.
|
|
137
|
-
|
|
138
|
-
For deeper architectural details, review the internal documentation:
|
|
139
|
-
- [docs/
|
|
140
|
-
- [docs/
|
|
141
|
-
- [docs/
|
|
142
|
-
- [docs/
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
1
|
+
# Foundry (`create-foundry-workspace`)
|
|
2
|
+
|
|
3
|
+
> **Deterministic, local-first, zero-LLM CLI for initializing AI-ready software development workspaces.**
|
|
4
|
+
|
|
5
|
+
[](https://github.com/Harshitkant21/Foundry/actions)
|
|
6
|
+
[](https://www.npmjs.com/package/create-foundry-workspace)
|
|
7
|
+
[](package.json)
|
|
8
|
+
[](LICENSE)
|
|
9
|
+
[](docs/architecture/ARCHITECTURE.md)
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
## What is Foundry?
|
|
14
|
+
|
|
15
|
+
Modern developers increasingly rely on AI coding assistants such as **Cursor**, **Claude Code**, and **GitHub Copilot**. However, when starting a new project, developers are forced to manually author and repeatedly recreate AI instruction files, project rules, context files, and guardrails.
|
|
16
|
+
|
|
17
|
+
**Foundry** is an open-source, deterministic developer tool distributed via npm as **`create-foundry-workspace`**. When starting a new project, running:
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
npx create-foundry-workspace
|
|
21
|
+
# or: npm create foundry-workspace
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
asks a minimal set of meaningful project initialization questions and deterministically scaffolds an **AI-ready software workspace** based on predefined, versioned, and maintainable templates.
|
|
25
|
+
|
|
26
|
+
---
|
|
27
|
+
|
|
28
|
+
## What Foundry Does
|
|
29
|
+
|
|
30
|
+
- **Establishes a Canonical Contract:** Generates a clean, machine-readable `ai-workspace.yaml` manifest.
|
|
31
|
+
- **Generates Native Tool Adapters:** Renders native, complete instruction files for **Cursor** (`.cursor/rules/*.mdc`), **Claude Code** (`CLAUDE.md`), **GitHub Copilot** (`.github/copilot-instructions.md`), and the cross-tool open standard **`AGENTS.md`**.
|
|
32
|
+
- **Generates Project README:** Creates a deterministic, human-facing `README.md` tailored to the selected project stack, commands, and AI tools.
|
|
33
|
+
- **Enforces Architectural Guardrails:** Injects strict or standard code quality, security, and testing guardrails.
|
|
34
|
+
- **Safeguards Existing Files:** Supports non-destructive safe-merges, overwrite protection, and dry-run execution.
|
|
35
|
+
|
|
36
|
+
---
|
|
37
|
+
|
|
38
|
+
## What Foundry Explicitly Does NOT Do
|
|
39
|
+
|
|
40
|
+
Foundry is **NOT an AI agent** and does not use AI to generate workspaces:
|
|
41
|
+
- ❌ **NO OpenAI, Anthropic, or Gemini API calls**
|
|
42
|
+
- ❌ **NO local or remote LLM inference**
|
|
43
|
+
- ❌ **NO network requests at runtime** (100% offline and local-first)
|
|
44
|
+
- ❌ **NO hidden API keys or telemetry**
|
|
45
|
+
- ❌ **NO autonomous code generation**
|
|
46
|
+
|
|
47
|
+
Output is 100% deterministic: given the same inputs and version, the generated workspace is bit-for-bit identical every time.
|
|
48
|
+
|
|
49
|
+
---
|
|
50
|
+
|
|
51
|
+
## Quick Start
|
|
52
|
+
|
|
53
|
+
Initialize an AI-ready workspace in your current directory:
|
|
54
|
+
```bash
|
|
55
|
+
npx create-foundry-workspace
|
|
56
|
+
# or: npm create foundry-workspace
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
Or target a specific folder:
|
|
60
|
+
```bash
|
|
61
|
+
npx create-foundry-workspace ./my-new-project
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
### Non-Interactive (CI / Scripted) Usage
|
|
65
|
+
```bash
|
|
66
|
+
npx create-foundry-workspace ./my-new-service \
|
|
67
|
+
--name my-new-service \
|
|
68
|
+
--type api \
|
|
69
|
+
--lang python \
|
|
70
|
+
--strictness strict \
|
|
71
|
+
--ai cursor,claude,copilot,agents \
|
|
72
|
+
--yes
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
---
|
|
76
|
+
|
|
77
|
+
## CLI Options
|
|
78
|
+
|
|
79
|
+
| Flag | Description | Default |
|
|
80
|
+
|---|---|---|
|
|
81
|
+
| `[directory]` | Target directory to initialize | `.` |
|
|
82
|
+
| `-n, --name <string>` | Project name | Inferred from folder or `package.json` |
|
|
83
|
+
| `-t, --type <type>` | `web` \| `api` \| `fullstack` \| `cli` \| `library` \| `data-ml` \| `mobile` \| `custom` | Inferred or `fullstack` |
|
|
84
|
+
| `-l, --lang <lang>` | `typescript` \| `javascript` \| `python` \| `java` \| `go` \| `rust` \| `other` | Inferred or `typescript` |
|
|
85
|
+
| `-r, --repo <repo>` | `github` \| `gitlab` \| `none` | `github` |
|
|
86
|
+
| `-s, --strictness <level>` | `standard` \| `strict` | `standard` |
|
|
87
|
+
| `-a, --ai <adapters>` | Comma-separated list of adapters (`claude,cursor,agents,copilot,codex,gemini,generic`) | `claude,cursor,agents` |
|
|
88
|
+
| `--dry-run` | Preview files in terminal without writing to disk | `false` |
|
|
89
|
+
| `-f, --force` | Overwrite conflicting files | `false` |
|
|
90
|
+
| `-y, --yes` | Accept all defaults non-interactively | `false` |
|
|
91
|
+
| `-v, --version` | Display version | |
|
|
92
|
+
| `-h, --help` | Display help | |
|
|
93
|
+
|
|
94
|
+
---
|
|
95
|
+
|
|
96
|
+
## Generated Workspace Structure
|
|
97
|
+
|
|
98
|
+
Foundry generates a deterministic, standardized workspace with zero bloat and zero hallucinations:
|
|
99
|
+
|
|
100
|
+
```text
|
|
101
|
+
my-project/
|
|
102
|
+
├── ai-workspace.yaml # Canonical machine contract
|
|
103
|
+
├── README.md # Project documentation & stack guide
|
|
104
|
+
├── AI_CONTEXT.md # System map and entrypoint for AI assistants
|
|
105
|
+
├── PROJECT_GUARDRAILS.md # Standalone architectural guardrails & testing rules
|
|
106
|
+
├── CONTRIBUTING.md # Contribution workflow & guidelines
|
|
107
|
+
├── .gitignore # Language-tailored ignore rules
|
|
108
|
+
│
|
|
109
|
+
├── .ai/ # AI operational intelligence directory
|
|
110
|
+
│ ├── context/project-context.md # Detailed project domain background
|
|
111
|
+
│ ├── rules/00-core-rules.md # Core workspace rules & coding conventions
|
|
112
|
+
│ ├── architecture/system-design.md # High-level architecture & layer boundaries
|
|
113
|
+
│ ├── decisions/0001-project-init.md # Initial architecture decision record (ADR)
|
|
114
|
+
│ └── tasks/task-backlog.md # Operational scratchpad for AI agent task execution
|
|
115
|
+
│
|
|
116
|
+
├── docs/ # Human-facing project documentation
|
|
117
|
+
│ ├── README.md # Documentation index
|
|
118
|
+
│ ├── ARCHITECTURE.md # System architecture & component design
|
|
119
|
+
│ ├── DEVELOPMENT.md # Local setup, run, and test guides
|
|
120
|
+
│ └── DECISIONS.md # Architecture decision records index
|
|
121
|
+
│
|
|
122
|
+
└── [Tool Adapters] # Based on user selection:
|
|
123
|
+
├── AGENTS.md # Multi-Agent open standard
|
|
124
|
+
├── CLAUDE.md # Anthropic Claude Code CLI
|
|
125
|
+
├── .cursor/rules/*.mdc # Cursor IDE modular rules
|
|
126
|
+
├── .github/copilot-instructions.md # GitHub Copilot context
|
|
127
|
+
├── .codex/instructions.md # OpenAI Codex context
|
|
128
|
+
├── GEMINI.md # Google Antigravity & Gemini CLI
|
|
129
|
+
└── AI_INSTRUCTIONS.md # Generic AI assistant instructions
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
---
|
|
133
|
+
|
|
134
|
+
## Repository Architecture & Dogfooding
|
|
135
|
+
|
|
136
|
+
Foundry strictly practices **dogfooding**. The Foundry repository itself is governed by the same `ai-workspace.yaml`, `AGENTS.md`, `CLAUDE.md`, `.cursor/`, and `.github/` files that it generates for users.
|
|
137
|
+
|
|
138
|
+
For deeper architectural details and future roadmap, review the internal documentation:
|
|
139
|
+
- [docs/README.md](docs/README.md) — Documentation index and repository guide.
|
|
140
|
+
- [docs/specifications/SPECIFICATION.md](docs/specifications/SPECIFICATION.md) — Formal specification of the AI Workspace contract.
|
|
141
|
+
- [docs/architecture/ARCHITECTURE.md](docs/architecture/ARCHITECTURE.md) — System architecture, module boundaries, and pure core pipeline.
|
|
142
|
+
- [docs/architecture/ADAPTERS.md](docs/architecture/ADAPTERS.md) — Adapter interface specification and registry design.
|
|
143
|
+
- [docs/development/DEVELOPMENT.md](docs/development/DEVELOPMENT.md) — Developer setup and testing guide.
|
|
144
|
+
- [docs/planning/FOUNDRY_AUDIT_AND_ROADMAP.md](docs/planning/FOUNDRY_AUDIT_AND_ROADMAP.md) — Comprehensive repository audit, architecture evaluation, and phased roadmap (Proposed / Working Plan — Pending Review).
|
|
145
|
+
|
|
146
|
+
---
|
|
147
|
+
|
|
148
|
+
## Development & Testing
|
|
149
|
+
|
|
150
|
+
```bash
|
|
151
|
+
# Clone the repository
|
|
152
|
+
git clone https://github.com/Harshitkant21/Foundry.git
|
|
153
|
+
cd Foundry
|
|
154
|
+
|
|
155
|
+
# Install dependencies
|
|
156
|
+
npm install
|
|
157
|
+
|
|
158
|
+
# Run Vitest test suite
|
|
159
|
+
npm test
|
|
160
|
+
|
|
161
|
+
# Build TypeScript to dist/
|
|
162
|
+
npm run build
|
|
163
|
+
|
|
164
|
+
# Run local development CLI
|
|
165
|
+
npx tsx src/bin.ts --dry-run
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
---
|
|
169
|
+
|
|
170
|
+
## License
|
|
171
|
+
|
|
172
|
+
MIT © Foundry Contributors
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { AppType, Language } from '../config/schema.js';
|
|
2
|
+
import { ProjectFileSystem } from '../io/scanner.js';
|
|
2
3
|
export interface DetectedEnvironment {
|
|
3
4
|
readonly name: string;
|
|
4
5
|
readonly language: Language;
|
|
@@ -7,7 +8,7 @@ export interface DetectedEnvironment {
|
|
|
7
8
|
}
|
|
8
9
|
/**
|
|
9
10
|
* Deterministically inspects target directory markers to infer project defaults.
|
|
10
|
-
*
|
|
11
|
+
* Decoupled from physical disk I/O via the ProjectFileSystem abstraction.
|
|
11
12
|
*/
|
|
12
|
-
export declare function detectProjectEnvironment(targetDir: string): Promise<DetectedEnvironment>;
|
|
13
|
+
export declare function detectProjectEnvironment(targetDir: string, projectFs?: ProjectFileSystem): Promise<DetectedEnvironment>;
|
|
13
14
|
//# sourceMappingURL=detector.d.ts.map
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import path from 'node:path';
|
|
2
2
|
import { DEFAULT_APP_TYPE, DEFAULT_LANGUAGE } from '../config/defaults.js';
|
|
3
|
+
import { NodeProjectFileSystem } from '../io/scanner.js';
|
|
3
4
|
import { detectGo } from './rules/go.js';
|
|
4
5
|
import { detectJava } from './rules/java.js';
|
|
5
6
|
import { detectNode } from './rules/node.js';
|
|
@@ -7,12 +8,13 @@ import { detectPython } from './rules/python.js';
|
|
|
7
8
|
import { detectRust } from './rules/rust.js';
|
|
8
9
|
/**
|
|
9
10
|
* Deterministically inspects target directory markers to infer project defaults.
|
|
10
|
-
*
|
|
11
|
+
* Decoupled from physical disk I/O via the ProjectFileSystem abstraction.
|
|
11
12
|
*/
|
|
12
|
-
export async function detectProjectEnvironment(targetDir) {
|
|
13
|
+
export async function detectProjectEnvironment(targetDir, projectFs) {
|
|
14
|
+
const fs = projectFs ?? new NodeProjectFileSystem(targetDir);
|
|
13
15
|
const fallbackName = path.basename(path.resolve(targetDir)) || 'my-workspace';
|
|
14
16
|
// Run detectors in priority order
|
|
15
|
-
const node = await detectNode(
|
|
17
|
+
const node = await detectNode(fs);
|
|
16
18
|
if (node) {
|
|
17
19
|
return {
|
|
18
20
|
name: node.name || fallbackName,
|
|
@@ -21,7 +23,7 @@ export async function detectProjectEnvironment(targetDir) {
|
|
|
21
23
|
isDetected: true,
|
|
22
24
|
};
|
|
23
25
|
}
|
|
24
|
-
const rust = await detectRust(
|
|
26
|
+
const rust = await detectRust(fs);
|
|
25
27
|
if (rust) {
|
|
26
28
|
return {
|
|
27
29
|
name: rust.name || fallbackName,
|
|
@@ -30,7 +32,7 @@ export async function detectProjectEnvironment(targetDir) {
|
|
|
30
32
|
isDetected: true,
|
|
31
33
|
};
|
|
32
34
|
}
|
|
33
|
-
const go = await detectGo(
|
|
35
|
+
const go = await detectGo(fs);
|
|
34
36
|
if (go) {
|
|
35
37
|
return {
|
|
36
38
|
name: go.name || fallbackName,
|
|
@@ -39,7 +41,7 @@ export async function detectProjectEnvironment(targetDir) {
|
|
|
39
41
|
isDetected: true,
|
|
40
42
|
};
|
|
41
43
|
}
|
|
42
|
-
const python = await detectPython(
|
|
44
|
+
const python = await detectPython(fs);
|
|
43
45
|
if (python) {
|
|
44
46
|
return {
|
|
45
47
|
name: fallbackName,
|
|
@@ -48,7 +50,7 @@ export async function detectProjectEnvironment(targetDir) {
|
|
|
48
50
|
isDetected: true,
|
|
49
51
|
};
|
|
50
52
|
}
|
|
51
|
-
const java = await detectJava(
|
|
53
|
+
const java = await detectJava(fs);
|
|
52
54
|
if (java) {
|
|
53
55
|
return {
|
|
54
56
|
name: fallbackName,
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { ProjectFileSystem } from '../../io/scanner.js';
|
|
1
2
|
import { DetectionCandidate } from './node.js';
|
|
2
|
-
export declare function detectGo(
|
|
3
|
+
export declare function detectGo(fs: ProjectFileSystem): Promise<DetectionCandidate | null>;
|
|
3
4
|
//# sourceMappingURL=go.d.ts.map
|
|
@@ -1,9 +1,7 @@
|
|
|
1
|
-
import fs from 'node:fs/promises';
|
|
2
1
|
import path from 'node:path';
|
|
3
|
-
export async function detectGo(
|
|
4
|
-
const goMod = path.join(targetDir, 'go.mod');
|
|
2
|
+
export async function detectGo(fs) {
|
|
5
3
|
try {
|
|
6
|
-
const raw = await fs.readFile(
|
|
4
|
+
const raw = await fs.readFile('go.mod');
|
|
7
5
|
const match = raw.match(/module\s+([^\s]+)/);
|
|
8
6
|
const modName = match && match[1] ? path.basename(match[1]) : undefined;
|
|
9
7
|
return {
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { ProjectFileSystem } from '../../io/scanner.js';
|
|
1
2
|
import { DetectionCandidate } from './node.js';
|
|
2
|
-
export declare function detectJava(
|
|
3
|
+
export declare function detectJava(fs: ProjectFileSystem): Promise<DetectionCandidate | null>;
|
|
3
4
|
//# sourceMappingURL=java.d.ts.map
|
|
@@ -1,27 +1,10 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
const
|
|
5
|
-
|
|
6
|
-
const gradleKts = path.join(targetDir, 'build.gradle.kts');
|
|
7
|
-
let hasJava = false;
|
|
8
|
-
try {
|
|
9
|
-
await fs.access(pomXml);
|
|
10
|
-
hasJava = true;
|
|
11
|
-
}
|
|
12
|
-
catch { }
|
|
13
|
-
try {
|
|
14
|
-
await fs.access(gradle);
|
|
15
|
-
hasJava = true;
|
|
16
|
-
}
|
|
17
|
-
catch { }
|
|
18
|
-
try {
|
|
19
|
-
await fs.access(gradleKts);
|
|
20
|
-
hasJava = true;
|
|
21
|
-
}
|
|
22
|
-
catch { }
|
|
23
|
-
if (!hasJava)
|
|
1
|
+
export async function detectJava(fs) {
|
|
2
|
+
const hasPom = await fs.exists('pom.xml');
|
|
3
|
+
const hasGradle = await fs.exists('build.gradle');
|
|
4
|
+
const hasGradleKts = await fs.exists('build.gradle.kts');
|
|
5
|
+
if (!hasPom && !hasGradle && !hasGradleKts) {
|
|
24
6
|
return null;
|
|
7
|
+
}
|
|
25
8
|
return {
|
|
26
9
|
language: 'java',
|
|
27
10
|
type: 'api',
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { AppType, Language } from '../../config/schema.js';
|
|
2
|
+
import { ProjectFileSystem } from '../../io/scanner.js';
|
|
2
3
|
export interface DetectionCandidate {
|
|
3
4
|
name?: string;
|
|
4
5
|
language?: Language;
|
|
5
6
|
type?: AppType;
|
|
6
7
|
}
|
|
7
|
-
export declare function detectNode(
|
|
8
|
+
export declare function detectNode(fs: ProjectFileSystem): Promise<DetectionCandidate | null>;
|
|
8
9
|
//# sourceMappingURL=node.d.ts.map
|
|
@@ -1,9 +1,6 @@
|
|
|
1
|
-
|
|
2
|
-
import path from 'node:path';
|
|
3
|
-
export async function detectNode(targetDir) {
|
|
4
|
-
const pkgPath = path.join(targetDir, 'package.json');
|
|
1
|
+
export async function detectNode(fs) {
|
|
5
2
|
try {
|
|
6
|
-
const raw = await fs.readFile(
|
|
3
|
+
const raw = await fs.readFile('package.json');
|
|
7
4
|
const pkg = JSON.parse(raw);
|
|
8
5
|
const name = typeof pkg.name === 'string' ? pkg.name.replace(/^@[^/]+\//, '') : undefined;
|
|
9
6
|
let language = 'javascript';
|
|
@@ -12,11 +9,9 @@ export async function detectNode(targetDir) {
|
|
|
12
9
|
(pkg.dependencies && pkg.dependencies.typescript)) {
|
|
13
10
|
language = 'typescript';
|
|
14
11
|
}
|
|
15
|
-
|
|
16
|
-
await fs.access(path.join(targetDir, 'tsconfig.json'));
|
|
12
|
+
if (await fs.exists('tsconfig.json')) {
|
|
17
13
|
language = 'typescript';
|
|
18
14
|
}
|
|
19
|
-
catch { }
|
|
20
15
|
let type = 'fullstack';
|
|
21
16
|
if (pkg.bin) {
|
|
22
17
|
type = 'cli';
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { ProjectFileSystem } from '../../io/scanner.js';
|
|
1
2
|
import { DetectionCandidate } from './node.js';
|
|
2
|
-
export declare function detectPython(
|
|
3
|
+
export declare function detectPython(fs: ProjectFileSystem): Promise<DetectionCandidate | null>;
|
|
3
4
|
//# sourceMappingURL=python.d.ts.map
|
|
@@ -1,27 +1,10 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
const
|
|
5
|
-
|
|
6
|
-
const setupPy = path.join(targetDir, 'setup.py');
|
|
7
|
-
let hasPython = false;
|
|
8
|
-
try {
|
|
9
|
-
await fs.access(pyproject);
|
|
10
|
-
hasPython = true;
|
|
11
|
-
}
|
|
12
|
-
catch { }
|
|
13
|
-
try {
|
|
14
|
-
await fs.access(requirements);
|
|
15
|
-
hasPython = true;
|
|
16
|
-
}
|
|
17
|
-
catch { }
|
|
18
|
-
try {
|
|
19
|
-
await fs.access(setupPy);
|
|
20
|
-
hasPython = true;
|
|
21
|
-
}
|
|
22
|
-
catch { }
|
|
23
|
-
if (!hasPython)
|
|
1
|
+
export async function detectPython(fs) {
|
|
2
|
+
const hasPyproject = await fs.exists('pyproject.toml');
|
|
3
|
+
const hasReqs = await fs.exists('requirements.txt');
|
|
4
|
+
const hasSetup = await fs.exists('setup.py');
|
|
5
|
+
if (!hasPyproject && !hasReqs && !hasSetup) {
|
|
24
6
|
return null;
|
|
7
|
+
}
|
|
25
8
|
return {
|
|
26
9
|
language: 'python',
|
|
27
10
|
type: 'api',
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { ProjectFileSystem } from '../../io/scanner.js';
|
|
1
2
|
import { DetectionCandidate } from './node.js';
|
|
2
|
-
export declare function detectRust(
|
|
3
|
+
export declare function detectRust(fs: ProjectFileSystem): Promise<DetectionCandidate | null>;
|
|
3
4
|
//# sourceMappingURL=rust.d.ts.map
|
|
@@ -1,9 +1,6 @@
|
|
|
1
|
-
|
|
2
|
-
import path from 'node:path';
|
|
3
|
-
export async function detectRust(targetDir) {
|
|
4
|
-
const cargoToml = path.join(targetDir, 'Cargo.toml');
|
|
1
|
+
export async function detectRust(fs) {
|
|
5
2
|
try {
|
|
6
|
-
const raw = await fs.readFile(
|
|
3
|
+
const raw = await fs.readFile('Cargo.toml');
|
|
7
4
|
const nameMatch = raw.match(/name\s*=\s*["']([^"']+)["']/);
|
|
8
5
|
const projName = nameMatch && nameMatch[1] ? nameMatch[1] : undefined;
|
|
9
6
|
return {
|
package/dist/index.d.ts
CHANGED
|
@@ -7,6 +7,7 @@ export * from './core/context.js';
|
|
|
7
7
|
export * from './core/generator.js';
|
|
8
8
|
export * from './core/interpolator.js';
|
|
9
9
|
export * from './io/paths.js';
|
|
10
|
+
export * from './io/scanner.js';
|
|
10
11
|
export * from './io/writer.js';
|
|
11
12
|
export * from './cli/command.js';
|
|
12
13
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.js
CHANGED
|
@@ -7,6 +7,7 @@ export * from './core/context.js';
|
|
|
7
7
|
export * from './core/generator.js';
|
|
8
8
|
export * from './core/interpolator.js';
|
|
9
9
|
export * from './io/paths.js';
|
|
10
|
+
export * from './io/scanner.js';
|
|
10
11
|
export * from './io/writer.js';
|
|
11
12
|
export * from './cli/command.js';
|
|
12
13
|
//# sourceMappingURL=index.js.map
|
package/dist/io/paths.js
CHANGED
|
@@ -13,7 +13,8 @@ export function normalizeRelativePath(filePath) {
|
|
|
13
13
|
export function resolveSafePath(baseDir, relativePath) {
|
|
14
14
|
const resolvedBase = path.resolve(baseDir);
|
|
15
15
|
const resolvedTarget = path.resolve(resolvedBase, relativePath);
|
|
16
|
-
|
|
16
|
+
const rel = path.relative(resolvedBase, resolvedTarget);
|
|
17
|
+
if (rel.startsWith('..') || path.isAbsolute(rel)) {
|
|
17
18
|
throw new Error(`Security Exception: Target path '${relativePath}' traverses outside base directory.`);
|
|
18
19
|
}
|
|
19
20
|
return resolvedTarget;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* File system scanner interface for project detection.
|
|
3
|
+
* Enforces pure core isolation by keeping disk access inside src/io/.
|
|
4
|
+
*/
|
|
5
|
+
export interface ProjectFileSystem {
|
|
6
|
+
readFile(relativePath: string): Promise<string>;
|
|
7
|
+
exists(relativePath: string): Promise<boolean>;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Default Node.js filesystem adapter for inspecting project directories.
|
|
11
|
+
*/
|
|
12
|
+
export declare class NodeProjectFileSystem implements ProjectFileSystem {
|
|
13
|
+
private readonly rootDir;
|
|
14
|
+
constructor(targetDir: string);
|
|
15
|
+
readFile(relativePath: string): Promise<string>;
|
|
16
|
+
exists(relativePath: string): Promise<boolean>;
|
|
17
|
+
}
|
|
18
|
+
//# sourceMappingURL=scanner.d.ts.map
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import fs from 'node:fs/promises';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
/**
|
|
4
|
+
* Default Node.js filesystem adapter for inspecting project directories.
|
|
5
|
+
*/
|
|
6
|
+
export class NodeProjectFileSystem {
|
|
7
|
+
rootDir;
|
|
8
|
+
constructor(targetDir) {
|
|
9
|
+
this.rootDir = path.resolve(targetDir);
|
|
10
|
+
}
|
|
11
|
+
async readFile(relativePath) {
|
|
12
|
+
const filePath = path.join(this.rootDir, relativePath);
|
|
13
|
+
return fs.readFile(filePath, 'utf-8');
|
|
14
|
+
}
|
|
15
|
+
async exists(relativePath) {
|
|
16
|
+
const filePath = path.join(this.rootDir, relativePath);
|
|
17
|
+
try {
|
|
18
|
+
await fs.access(filePath);
|
|
19
|
+
return true;
|
|
20
|
+
}
|
|
21
|
+
catch {
|
|
22
|
+
return false;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
//# sourceMappingURL=scanner.js.map
|
|
@@ -1,19 +1,67 @@
|
|
|
1
1
|
export function renderContributing(context) {
|
|
2
2
|
const { config, commands } = context;
|
|
3
|
-
return `# Contributing to ${config.project.name}
|
|
4
|
-
|
|
5
|
-
Thank you for contributing! Please
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
3
|
+
return `# Contributing to ${config.project.name}
|
|
4
|
+
|
|
5
|
+
Thank you for contributing! Please adhere to our standardized engineering standards, branch conventions, and AI guardrails.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## 1. Development & Quality Workflow
|
|
10
|
+
|
|
11
|
+
Before submitting changes, ensure your working tree passes all verification gates:
|
|
12
|
+
|
|
13
|
+
1. **Install dependencies & build:**
|
|
14
|
+
\`\`\`bash
|
|
15
|
+
${commands.build}
|
|
16
|
+
\`\`\`
|
|
17
|
+
2. **Format and lint:**
|
|
18
|
+
\`\`\`bash
|
|
19
|
+
${commands.lint}
|
|
20
|
+
\`\`\`
|
|
21
|
+
3. **Execute automated tests:**
|
|
22
|
+
\`\`\`bash
|
|
23
|
+
${commands.test}
|
|
24
|
+
\`\`\`
|
|
25
|
+
|
|
26
|
+
---
|
|
27
|
+
|
|
28
|
+
## 2. Branching & Conventional Commits
|
|
29
|
+
|
|
30
|
+
We follow the [Conventional Commits](https://www.conventionalcommits.org/) specification for structured, machine-parseable commit history:
|
|
31
|
+
|
|
32
|
+
### Commit Format
|
|
33
|
+
\`\`\`text
|
|
34
|
+
<type>(<scope>): <short description>
|
|
35
|
+
\`\`\`
|
|
36
|
+
|
|
37
|
+
### Allowed Types
|
|
38
|
+
- **\`feat\`**: A new feature or capability.
|
|
39
|
+
- **\`fix\`**: A bug or regression fix.
|
|
40
|
+
- **\`docs\`**: Documentation additions or corrections.
|
|
41
|
+
- **\`refactor\`**: Code change that neither fixes a bug nor adds a feature.
|
|
42
|
+
- **\`test\`**: Adding or modifying test suites.
|
|
43
|
+
- **\`chore\`**: Tooling, dependencies, or repository maintenance.
|
|
44
|
+
|
|
45
|
+
### Branch Naming Patterns
|
|
46
|
+
- \`feature/<short-topic>\` (e.g. \`feature/langgraph-adapter\`)
|
|
47
|
+
- \`bugfix/<issue-number>-<short-topic>\` (e.g. \`bugfix/102-path-traversal\`)
|
|
48
|
+
- \`docs/<topic>\` (e.g. \`docs/contributing-guide\`)
|
|
49
|
+
|
|
50
|
+
---
|
|
51
|
+
|
|
52
|
+
## 3. Pull Request Standards
|
|
53
|
+
|
|
54
|
+
- **Atomic Scope:** PRs must focus on a single conceptual change. Avoid bundling unrelated refactorings.
|
|
55
|
+
- **Zero Broken Tests:** All tests must pass before opening a PR.
|
|
56
|
+
- **Commit History:** Keep history clean and avoid merge commits where squashing or rebasing is requested.
|
|
57
|
+
|
|
58
|
+
---
|
|
59
|
+
|
|
60
|
+
## 4. AI-Assisted Contributions
|
|
61
|
+
|
|
62
|
+
- When using AI coding assistants (Cursor, Claude, Copilot, Antigravity, etc.), verify they strictly adhere to [\`PROJECT_GUARDRAILS.md\`](./PROJECT_GUARDRAILS.md).
|
|
63
|
+
- AI agents operate in **read-only Git mode by default**. Never allow an agent to stage or commit code without manual human inspection.
|
|
64
|
+
- All AI-generated code must be fully understood, tested, and reviewed by a human maintainer.
|
|
17
65
|
`;
|
|
18
66
|
}
|
|
19
67
|
//# sourceMappingURL=contributing.js.map
|
package/package.json
CHANGED
|
@@ -1,64 +1,64 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "create-foundry-workspace",
|
|
3
|
-
"version": "1.0.
|
|
4
|
-
"description": "Deterministic, local-first CLI for scaffolding AI-ready software workspaces.",
|
|
5
|
-
"type": "module",
|
|
6
|
-
"main": "./dist/index.js",
|
|
7
|
-
"bin": {
|
|
8
|
-
"create-foundry-workspace": "./bin/index.js",
|
|
9
|
-
"foundry": "./bin/index.js"
|
|
10
|
-
},
|
|
11
|
-
"files": [
|
|
12
|
-
"dist",
|
|
13
|
-
"!dist/**/*.map",
|
|
14
|
-
"bin",
|
|
15
|
-
"README.md",
|
|
16
|
-
"LICENSE"
|
|
17
|
-
],
|
|
18
|
-
"scripts": {
|
|
19
|
-
"build": "tsc",
|
|
20
|
-
"dev": "tsx src/bin.ts",
|
|
21
|
-
"prepublishOnly": "npm run build && npm test",
|
|
22
|
-
"test": "vitest run",
|
|
23
|
-
"test:watch": "vitest",
|
|
24
|
-
"typecheck": "tsc --noEmit"
|
|
25
|
-
},
|
|
26
|
-
"keywords": [
|
|
27
|
-
"ai",
|
|
28
|
-
"ai-coding",
|
|
29
|
-
"developer-tools",
|
|
30
|
-
"cli",
|
|
31
|
-
"scaffolding",
|
|
32
|
-
"workspace",
|
|
33
|
-
"developer-experience",
|
|
34
|
-
"cursor",
|
|
35
|
-
"claude",
|
|
36
|
-
"copilot",
|
|
37
|
-
"codex",
|
|
38
|
-
"gemini"
|
|
39
|
-
],
|
|
40
|
-
"author": "Harshit Kant",
|
|
41
|
-
"repository": {
|
|
42
|
-
"type": "git",
|
|
43
|
-
"url": "git+https://github.com/Harshitkant21/Foundry.git"
|
|
44
|
-
},
|
|
45
|
-
"bugs": {
|
|
46
|
-
"url": "https://github.com/Harshitkant21/Foundry/issues"
|
|
47
|
-
},
|
|
48
|
-
"homepage": "https://github.com/Harshitkant21/Foundry#readme",
|
|
49
|
-
"license": "MIT",
|
|
50
|
-
"dependencies": {
|
|
51
|
-
"@clack/prompts": "^0.8.2",
|
|
52
|
-
"commander": "^12.1.0",
|
|
53
|
-
"zod": "^3.23.8"
|
|
54
|
-
},
|
|
55
|
-
"devDependencies": {
|
|
56
|
-
"@types/node": "^22.5.5",
|
|
57
|
-
"tsx": "^4.19.1",
|
|
58
|
-
"typescript": "^5.6.2",
|
|
59
|
-
"vitest": "^2.1.1"
|
|
60
|
-
},
|
|
61
|
-
"engines": {
|
|
62
|
-
"node": ">=18.0.0"
|
|
63
|
-
}
|
|
64
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "create-foundry-workspace",
|
|
3
|
+
"version": "1.0.1",
|
|
4
|
+
"description": "Deterministic, local-first CLI for scaffolding AI-ready software workspaces.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"bin": {
|
|
8
|
+
"create-foundry-workspace": "./bin/index.js",
|
|
9
|
+
"foundry": "./bin/index.js"
|
|
10
|
+
},
|
|
11
|
+
"files": [
|
|
12
|
+
"dist",
|
|
13
|
+
"!dist/**/*.map",
|
|
14
|
+
"bin",
|
|
15
|
+
"README.md",
|
|
16
|
+
"LICENSE"
|
|
17
|
+
],
|
|
18
|
+
"scripts": {
|
|
19
|
+
"build": "tsc",
|
|
20
|
+
"dev": "tsx src/bin.ts",
|
|
21
|
+
"prepublishOnly": "npm run build && npm test",
|
|
22
|
+
"test": "vitest run",
|
|
23
|
+
"test:watch": "vitest",
|
|
24
|
+
"typecheck": "tsc --noEmit"
|
|
25
|
+
},
|
|
26
|
+
"keywords": [
|
|
27
|
+
"ai",
|
|
28
|
+
"ai-coding",
|
|
29
|
+
"developer-tools",
|
|
30
|
+
"cli",
|
|
31
|
+
"scaffolding",
|
|
32
|
+
"workspace",
|
|
33
|
+
"developer-experience",
|
|
34
|
+
"cursor",
|
|
35
|
+
"claude",
|
|
36
|
+
"copilot",
|
|
37
|
+
"codex",
|
|
38
|
+
"gemini"
|
|
39
|
+
],
|
|
40
|
+
"author": "Harshit Kant",
|
|
41
|
+
"repository": {
|
|
42
|
+
"type": "git",
|
|
43
|
+
"url": "git+https://github.com/Harshitkant21/Foundry.git"
|
|
44
|
+
},
|
|
45
|
+
"bugs": {
|
|
46
|
+
"url": "https://github.com/Harshitkant21/Foundry/issues"
|
|
47
|
+
},
|
|
48
|
+
"homepage": "https://github.com/Harshitkant21/Foundry#readme",
|
|
49
|
+
"license": "MIT",
|
|
50
|
+
"dependencies": {
|
|
51
|
+
"@clack/prompts": "^0.8.2",
|
|
52
|
+
"commander": "^12.1.0",
|
|
53
|
+
"zod": "^3.23.8"
|
|
54
|
+
},
|
|
55
|
+
"devDependencies": {
|
|
56
|
+
"@types/node": "^22.5.5",
|
|
57
|
+
"tsx": "^4.19.1",
|
|
58
|
+
"typescript": "^5.6.2",
|
|
59
|
+
"vitest": "^2.1.1"
|
|
60
|
+
},
|
|
61
|
+
"engines": {
|
|
62
|
+
"node": ">=18.0.0"
|
|
63
|
+
}
|
|
64
|
+
}
|