scaffinity 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/LICENSE +21 -0
- package/README.md +202 -0
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +400 -0
- package/dist/index.d.ts +30 -0
- package/dist/index.js +358 -0
- package/package.json +49 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Deviprasad Rai P
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
# ⚡ Scaffinity
|
|
2
|
+
|
|
3
|
+
> Generate, export, and share project structures as portable JSON blueprints — with a single command.
|
|
4
|
+
|
|
5
|
+
[](https://www.npmjs.com/package/scaffinity)
|
|
6
|
+
[](https://opensource.org/licenses/MIT)
|
|
7
|
+
[](https://github.com/devi5040/scaffinity/pulls)
|
|
8
|
+
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
## The Problem
|
|
12
|
+
|
|
13
|
+
Every new project starts the same way — manually creating folders, files, config boilerplate. You either do it by hand each time or maintain a growing list of templates nobody on your team can find.
|
|
14
|
+
|
|
15
|
+
**Scaffinity fixes this.** Define your entire project structure in a single JSON file. Generate it anywhere. Share it with anyone.
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
## Install
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
npm install -g scaffinity
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
## Three Commands
|
|
28
|
+
|
|
29
|
+
### 1. `generate` — JSON blueprint → real project
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
scaffinity generate blueprint.json
|
|
33
|
+
scaffinity generate blueprint.json -o ./my-new-project
|
|
34
|
+
scaffinity generate blueprint.json --dry --verbose # preview first
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
### 2. `export` — existing project → JSON blueprint
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
scaffinity export ./my-project -o blueprint.json
|
|
41
|
+
scaffinity export . --ignore dist coverage --depth 4
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
### 3. `init` — build a blueprint interactively
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
scaffinity init
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
---
|
|
51
|
+
|
|
52
|
+
## Blueprint Format
|
|
53
|
+
|
|
54
|
+
A blueprint is just a JSON file. Objects are directories, strings or `null` are files.
|
|
55
|
+
|
|
56
|
+
```json
|
|
57
|
+
{
|
|
58
|
+
"src": {
|
|
59
|
+
"modules": {
|
|
60
|
+
"auth": {
|
|
61
|
+
"auth.controller.ts": "",
|
|
62
|
+
"auth.service.ts": "",
|
|
63
|
+
"auth.routes.ts": ""
|
|
64
|
+
}
|
|
65
|
+
},
|
|
66
|
+
"middleware": {
|
|
67
|
+
"error.middleware.ts": ""
|
|
68
|
+
},
|
|
69
|
+
"main.ts": "import express from 'express'\n\nconst app = express()\n"
|
|
70
|
+
},
|
|
71
|
+
".env.example": "PORT=3000\nNODE_ENV=development",
|
|
72
|
+
".gitignore": "node_modules\ndist\n.env\n"
|
|
73
|
+
}
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
Files with string values get that content written directly. `null` creates an empty file.
|
|
77
|
+
|
|
78
|
+
---
|
|
79
|
+
|
|
80
|
+
## Real World Example
|
|
81
|
+
|
|
82
|
+
**Generate a full Express + TypeScript API structure:**
|
|
83
|
+
|
|
84
|
+
```bash
|
|
85
|
+
# Download the community blueprint
|
|
86
|
+
curl -O https://raw.githubusercontent.com/devi5040/scaffinity/main/examples/express-ts-api.json
|
|
87
|
+
|
|
88
|
+
# Generate the project
|
|
89
|
+
scaffinity generate express-ts-api.json -o ./my-api
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
Output:
|
|
93
|
+
|
|
94
|
+
```
|
|
95
|
+
✅ Blueprint loaded
|
|
96
|
+
|
|
97
|
+
mkdir: my-api/src
|
|
98
|
+
mkdir: my-api/src/modules
|
|
99
|
+
mkdir: my-api/src/modules/auth
|
|
100
|
+
create: my-api/src/modules/auth/auth.controller.ts
|
|
101
|
+
create: my-api/src/modules/auth/auth.service.ts
|
|
102
|
+
...
|
|
103
|
+
|
|
104
|
+
📦 Scaffinity Summary
|
|
105
|
+
────────────────────────────────────────
|
|
106
|
+
✓ Created : 25 files/dirs
|
|
107
|
+
────────────────────────────────────────
|
|
108
|
+
|
|
109
|
+
✅ Project structure generated at: ./my-api
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
**Export your current project as a shareable blueprint:**
|
|
113
|
+
|
|
114
|
+
```bash
|
|
115
|
+
scaffinity export ./my-project -o team-blueprint.json
|
|
116
|
+
# Share team-blueprint.json with your team — they run one command to replicate your structure
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
---
|
|
120
|
+
|
|
121
|
+
## Options
|
|
122
|
+
|
|
123
|
+
### `generate`
|
|
124
|
+
|
|
125
|
+
| Option | Description |
|
|
126
|
+
| -------------------- | --------------------------------------------- |
|
|
127
|
+
| `-o, --output <dir>` | Output directory (default: current directory) |
|
|
128
|
+
| `-d, --dry` | Dry run — preview without creating files |
|
|
129
|
+
| `-v, --verbose` | Show each file/folder being created |
|
|
130
|
+
|
|
131
|
+
### `export`
|
|
132
|
+
|
|
133
|
+
| Option | Description |
|
|
134
|
+
| ---------------------------- | ---------------------------------------- |
|
|
135
|
+
| `-o, --output <file>` | Save blueprint to file (default: stdout) |
|
|
136
|
+
| `-i, --ignore <patterns...>` | Additional patterns to ignore |
|
|
137
|
+
| `--depth <number>` | Maximum directory depth (default: 10) |
|
|
138
|
+
|
|
139
|
+
---
|
|
140
|
+
|
|
141
|
+
## Community Blueprints
|
|
142
|
+
|
|
143
|
+
Ready-made blueprints in the [`/examples`](./examples) folder:
|
|
144
|
+
|
|
145
|
+
| Blueprint | Description |
|
|
146
|
+
| ------------------------------------------------------- | ----------------------------- |
|
|
147
|
+
| [`express-ts-api.json`](./examples/express-ts-api.json) | Express + TypeScript REST API |
|
|
148
|
+
| [`nextjs-app.json`](./examples/nextjs-app.json) | Next.js App Router project |
|
|
149
|
+
|
|
150
|
+
**Have a blueprint to share?** Open a PR and add it to `/examples`.
|
|
151
|
+
|
|
152
|
+
---
|
|
153
|
+
|
|
154
|
+
## Programmatic Usage
|
|
155
|
+
|
|
156
|
+
```typescript
|
|
157
|
+
import { generateCommand, exportCommand } from "scaffinity";
|
|
158
|
+
|
|
159
|
+
// Generate from a blueprint object directly
|
|
160
|
+
await generateCommand("blueprint.json", {
|
|
161
|
+
output: "./my-project",
|
|
162
|
+
verbose: true,
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
// Export a project to blueprint
|
|
166
|
+
await exportCommand("./existing-project", {
|
|
167
|
+
output: "blueprint.json",
|
|
168
|
+
ignore: ["dist", "coverage"],
|
|
169
|
+
});
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
---
|
|
173
|
+
|
|
174
|
+
## Why Scaffinity over Yeoman / degit / framework CLIs?
|
|
175
|
+
|
|
176
|
+
| | Scaffinity | Yeoman | degit | Framework CLIs |
|
|
177
|
+
| ----------------------- | ---------- | ------ | ----- | -------------- |
|
|
178
|
+
| Language agnostic | ✅ | ✅ | ✅ | ❌ |
|
|
179
|
+
| Portable JSON format | ✅ | ❌ | ❌ | ❌ |
|
|
180
|
+
| Export existing project | ✅ | ❌ | ❌ | ❌ |
|
|
181
|
+
| Zero config | ✅ | ❌ | ✅ | ✅ |
|
|
182
|
+
| File content support | ✅ | ✅ | ✅ | ✅ |
|
|
183
|
+
| Shareable blueprint | ✅ | ❌ | ✅ | ❌ |
|
|
184
|
+
|
|
185
|
+
---
|
|
186
|
+
|
|
187
|
+
## Contributing
|
|
188
|
+
|
|
189
|
+
PRs are welcome. To add a community blueprint, add a JSON file to `/examples` and open a PR.
|
|
190
|
+
|
|
191
|
+
```bash
|
|
192
|
+
git clone https://github.com/devi5040/scaffinity
|
|
193
|
+
cd scaffinity
|
|
194
|
+
npm install
|
|
195
|
+
npm run dev -- generate examples/express-ts-api.json --dry --verbose
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
---
|
|
199
|
+
|
|
200
|
+
## License
|
|
201
|
+
|
|
202
|
+
MIT © [Deviprasad Rai](https://github.com/devi5040)
|
package/dist/cli.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
package/dist/cli.js
ADDED
|
@@ -0,0 +1,400 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
var __create = Object.create;
|
|
4
|
+
var __defProp = Object.defineProperty;
|
|
5
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
6
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
7
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
8
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
9
|
+
var __copyProps = (to, from, except, desc) => {
|
|
10
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
11
|
+
for (let key of __getOwnPropNames(from))
|
|
12
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
13
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
14
|
+
}
|
|
15
|
+
return to;
|
|
16
|
+
};
|
|
17
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
18
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
19
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
20
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
21
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
22
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
23
|
+
mod
|
|
24
|
+
));
|
|
25
|
+
|
|
26
|
+
// src/cli.ts
|
|
27
|
+
var import_chalk4 = __toESM(require("chalk"));
|
|
28
|
+
var import_commander = require("commander");
|
|
29
|
+
|
|
30
|
+
// src/commands/generate.ts
|
|
31
|
+
var import_path = __toESM(require("path"));
|
|
32
|
+
var import_chalk = __toESM(require("chalk"));
|
|
33
|
+
var import_fs_extra = __toESM(require("fs-extra"));
|
|
34
|
+
var import_ora = __toESM(require("ora"));
|
|
35
|
+
function isFileContent(value) {
|
|
36
|
+
return typeof value === "string" || value === null || typeof value === "object" && value != null && "template" in value;
|
|
37
|
+
}
|
|
38
|
+
function resolveContent(value) {
|
|
39
|
+
if (value === null || value === void 0) return "";
|
|
40
|
+
if (typeof value === "string") return value;
|
|
41
|
+
if (typeof value === "object" && "template" in value) {
|
|
42
|
+
const v = value;
|
|
43
|
+
return `//Generated from template: ${v.template}
|
|
44
|
+
// Options: ${JSON.stringify(v.options ?? {}, null, 2)}
|
|
45
|
+
`;
|
|
46
|
+
}
|
|
47
|
+
return "";
|
|
48
|
+
}
|
|
49
|
+
async function processNode(node, currentPath, result, options) {
|
|
50
|
+
for (const [key, value] of Object.entries(node)) {
|
|
51
|
+
const fullPath = import_path.default.join(currentPath, key);
|
|
52
|
+
if (isFileContent(value)) {
|
|
53
|
+
if (options.dry) {
|
|
54
|
+
result.created.push(fullPath);
|
|
55
|
+
if (options.verbose) {
|
|
56
|
+
console.log(
|
|
57
|
+
import_chalk.default.cyan(" [dry] create file:"),
|
|
58
|
+
import_chalk.default.white(fullPath)
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
} else {
|
|
62
|
+
try {
|
|
63
|
+
await import_fs_extra.default.ensureDir(import_path.default.dirname(fullPath));
|
|
64
|
+
if (await import_fs_extra.default.pathExists(fullPath)) {
|
|
65
|
+
result.skipped.push(fullPath);
|
|
66
|
+
if (options.verbose)
|
|
67
|
+
console.log(import_chalk.default.yellow(" skip:"), import_chalk.default.white(fullPath));
|
|
68
|
+
} else {
|
|
69
|
+
await import_fs_extra.default.writeFile(fullPath, resolveContent(value), "utf-8");
|
|
70
|
+
result.created.push(fullPath);
|
|
71
|
+
if (options.verbose)
|
|
72
|
+
console.log(import_chalk.default.green(" create:"), import_chalk.default.white(fullPath));
|
|
73
|
+
}
|
|
74
|
+
} catch (error) {
|
|
75
|
+
result.errors.push(fullPath);
|
|
76
|
+
console.log(import_chalk.default.red(" error:"), import_chalk.default.white(fullPath));
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
} else if (typeof value === "object" && value !== null) {
|
|
80
|
+
if (!options.dry) await import_fs_extra.default.ensureDir(fullPath);
|
|
81
|
+
if (options.verbose)
|
|
82
|
+
console.log(import_chalk.default.blue(" makedir:"), import_chalk.default.white(fullPath));
|
|
83
|
+
await processNode(value, fullPath, result, options);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
async function generateCommand(jsonFile, options) {
|
|
88
|
+
const spinner = (0, import_ora.default)({
|
|
89
|
+
text: "Reading blueprint...",
|
|
90
|
+
stream: process.stdout
|
|
91
|
+
}).start();
|
|
92
|
+
try {
|
|
93
|
+
const absoluteJson = import_path.default.resolve(process.cwd(), jsonFile);
|
|
94
|
+
if (!await import_fs_extra.default.pathExists(absoluteJson)) {
|
|
95
|
+
spinner.fail(import_chalk.default.red(`Blueprint file not found: ${jsonFile}`));
|
|
96
|
+
process.exit(1);
|
|
97
|
+
}
|
|
98
|
+
const raw = await import_fs_extra.default.readFile(absoluteJson, "utf-8");
|
|
99
|
+
let structre;
|
|
100
|
+
try {
|
|
101
|
+
structre = JSON.parse(raw);
|
|
102
|
+
} catch {
|
|
103
|
+
spinner.fail(import_chalk.default.red("Invalid Json in blueprint file"));
|
|
104
|
+
process.exit(1);
|
|
105
|
+
}
|
|
106
|
+
const outputDir = import_path.default.resolve(process.cwd(), options.output ?? ".");
|
|
107
|
+
await import_fs_extra.default.ensureDir(outputDir);
|
|
108
|
+
spinner.succeed("Blueprint loaded");
|
|
109
|
+
if (options.dry)
|
|
110
|
+
console.log(import_chalk.default.yellow("\nDry-run - no files will be created\n"));
|
|
111
|
+
if (options.verbose) console.log(import_chalk.default.dim("\nProcessing Structure:\n"));
|
|
112
|
+
const result = { created: [], skipped: [], errors: [] };
|
|
113
|
+
await processNode(structre, outputDir, result, options);
|
|
114
|
+
console.log("\n" + import_chalk.default.bold("Scaffinity summary"));
|
|
115
|
+
console.log(import_chalk.default.dim("-".repeat(40)));
|
|
116
|
+
console.log(
|
|
117
|
+
import_chalk.default.green(` \u2713 Created : ${result.created.length} files/dirs`)
|
|
118
|
+
);
|
|
119
|
+
if (result.skipped.length > 0)
|
|
120
|
+
console.log(
|
|
121
|
+
import_chalk.default.yellow(` \u26A0 Skipped : ${result.skipped.length} (already exist)`)
|
|
122
|
+
);
|
|
123
|
+
if (result.errors.length > 0)
|
|
124
|
+
console.log(import_chalk.default.red(` \u2717 Errors : ${result.errors.length}`));
|
|
125
|
+
console.log(import_chalk.default.dim("-".repeat(40)));
|
|
126
|
+
if (!options.dry && result.created.length > 0)
|
|
127
|
+
console.log(
|
|
128
|
+
import_chalk.default.green(
|
|
129
|
+
`
|
|
130
|
+
\u2705 Project structure generated at: ${import_chalk.default.bold(outputDir)}
|
|
131
|
+
`
|
|
132
|
+
)
|
|
133
|
+
);
|
|
134
|
+
} catch (error) {
|
|
135
|
+
spinner.fail(import_chalk.default.red("Generation failed"));
|
|
136
|
+
console.error(error);
|
|
137
|
+
process.exit(1);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// src/commands/export.ts
|
|
142
|
+
var import_path2 = __toESM(require("path"));
|
|
143
|
+
var import_fs_extra2 = __toESM(require("fs-extra"));
|
|
144
|
+
var import_chalk2 = __toESM(require("chalk"));
|
|
145
|
+
var import_ora2 = __toESM(require("ora"));
|
|
146
|
+
var DEFAULT_IGNORE = [
|
|
147
|
+
"node_modules",
|
|
148
|
+
".git",
|
|
149
|
+
"dist",
|
|
150
|
+
"build",
|
|
151
|
+
".next",
|
|
152
|
+
".nuxt",
|
|
153
|
+
"coverage",
|
|
154
|
+
".DS_Store",
|
|
155
|
+
"Thumbs.db",
|
|
156
|
+
"*.log",
|
|
157
|
+
".env",
|
|
158
|
+
".env.local"
|
|
159
|
+
];
|
|
160
|
+
function matchesIgnore(name, ignoreList) {
|
|
161
|
+
return ignoreList.some((pattern) => {
|
|
162
|
+
if (pattern.startsWith("*")) return name.endsWith(pattern.slice(1));
|
|
163
|
+
return name === pattern;
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
async function buildStructure(dirPath, ignoreList, currentDepth, maxDepth) {
|
|
167
|
+
const result = {};
|
|
168
|
+
if (currentDepth > maxDepth) return result;
|
|
169
|
+
const entries = await import_fs_extra2.default.readdir(dirPath, { withFileTypes: true });
|
|
170
|
+
for (const entry of entries) {
|
|
171
|
+
if (matchesIgnore(entry.name, ignoreList)) continue;
|
|
172
|
+
const fullPath = import_path2.default.join(dirPath, entry.name);
|
|
173
|
+
if (entry.isDirectory())
|
|
174
|
+
result[entry.name] = await buildStructure(
|
|
175
|
+
fullPath,
|
|
176
|
+
ignoreList,
|
|
177
|
+
currentDepth + 1,
|
|
178
|
+
maxDepth
|
|
179
|
+
);
|
|
180
|
+
else if (entry.isFile()) result[entry.name] = null;
|
|
181
|
+
}
|
|
182
|
+
return result;
|
|
183
|
+
}
|
|
184
|
+
async function exportCommand(projectPath, options) {
|
|
185
|
+
const spinner = (0, import_ora2.default)("Scanning project structure...").start();
|
|
186
|
+
try {
|
|
187
|
+
const absolutePath = import_path2.default.resolve(process.cwd(), projectPath);
|
|
188
|
+
if (!await import_fs_extra2.default.pathExists(absolutePath)) {
|
|
189
|
+
spinner.fail(import_chalk2.default.red(`Path not found: ${projectPath}`));
|
|
190
|
+
process.exit(1);
|
|
191
|
+
}
|
|
192
|
+
const stat = await import_fs_extra2.default.stat(absolutePath);
|
|
193
|
+
if (!stat.isDirectory()) {
|
|
194
|
+
spinner.fail(import_chalk2.default.red(`Path is not a directory: ${projectPath}`));
|
|
195
|
+
process.exit(1);
|
|
196
|
+
}
|
|
197
|
+
const ignoreList = [...DEFAULT_IGNORE, ...options.ignore ?? []];
|
|
198
|
+
const maxDepth = options.depth ?? 10;
|
|
199
|
+
const structure = await buildStructure(
|
|
200
|
+
absolutePath,
|
|
201
|
+
ignoreList,
|
|
202
|
+
0,
|
|
203
|
+
maxDepth
|
|
204
|
+
);
|
|
205
|
+
spinner.succeed("Structure scanned");
|
|
206
|
+
const json = JSON.stringify(structure, null, 2);
|
|
207
|
+
if (options.output) {
|
|
208
|
+
const outputPath = import_path2.default.resolve(process.cwd(), options.output);
|
|
209
|
+
await import_fs_extra2.default.writeFile(outputPath, json, "utf-8");
|
|
210
|
+
console.log(
|
|
211
|
+
import_chalk2.default.green(`
|
|
212
|
+
\u2705 Blueprint exported to: ${import_chalk2.default.bold(outputPath)}
|
|
213
|
+
`)
|
|
214
|
+
);
|
|
215
|
+
} else
|
|
216
|
+
console.log(json);
|
|
217
|
+
const fileCount = (json.match(/:null/g) ?? []).length;
|
|
218
|
+
const dirCount = Object.keys(structure).length;
|
|
219
|
+
if (options.output) {
|
|
220
|
+
console.log(
|
|
221
|
+
import_chalk2.default.dim(` Directories: ${dirCount} | Files: ${fileCount}`)
|
|
222
|
+
);
|
|
223
|
+
console.log(import_chalk2.default.dim(` Ignored: ${ignoreList.join(", ")}
|
|
224
|
+
`));
|
|
225
|
+
}
|
|
226
|
+
} catch (error) {
|
|
227
|
+
spinner.fail(import_chalk2.default.red("Export failed"));
|
|
228
|
+
console.error(error);
|
|
229
|
+
process.exit(1);
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
// src/commands/init.ts
|
|
234
|
+
var import_chalk3 = __toESM(require("chalk"));
|
|
235
|
+
var import_prompts = __toESM(require("prompts"));
|
|
236
|
+
var import_path3 = __toESM(require("path"));
|
|
237
|
+
var import_fs_extra3 = __toESM(require("fs-extra"));
|
|
238
|
+
async function buildInteractive() {
|
|
239
|
+
const root = {};
|
|
240
|
+
const queue = [{ node: root, currentPath: "/", label: "root" }];
|
|
241
|
+
console.log(import_chalk3.default.bold.cyan("\n Scaffinity Interactive Builder"));
|
|
242
|
+
console.log(import_chalk3.default.dim("Build your project structure step by step.\n"));
|
|
243
|
+
console.log(
|
|
244
|
+
import_chalk3.default.dim("Commands: add file -> type name with extension (e.g. index.ts)")
|
|
245
|
+
);
|
|
246
|
+
console.log(
|
|
247
|
+
import_chalk3.default.dim(" add dir -> type name without extension (e.g. src)")
|
|
248
|
+
);
|
|
249
|
+
console.log(import_chalk3.default.dim(" done -> finish current directory\n"));
|
|
250
|
+
while (queue.length > 0) {
|
|
251
|
+
const current = queue.shift();
|
|
252
|
+
console.log(import_chalk3.default.blue(`
|
|
253
|
+
${current?.label} (${current?.currentPath})`));
|
|
254
|
+
let continueAdding = true;
|
|
255
|
+
while (continueAdding) {
|
|
256
|
+
const { action } = await (0, import_prompts.default)({
|
|
257
|
+
type: "select",
|
|
258
|
+
name: "action",
|
|
259
|
+
message: `Add to ${import_chalk3.default.cyan(current.label)}:`,
|
|
260
|
+
choices: [
|
|
261
|
+
{ title: "Add file", value: "file" },
|
|
262
|
+
{ title: "Add directory", value: "dir" },
|
|
263
|
+
{ title: "Done with this directory", value: "done" }
|
|
264
|
+
]
|
|
265
|
+
});
|
|
266
|
+
if (!action || action === "done") {
|
|
267
|
+
continueAdding = false;
|
|
268
|
+
break;
|
|
269
|
+
}
|
|
270
|
+
if (action === "file") {
|
|
271
|
+
const { fileName } = await (0, import_prompts.default)({
|
|
272
|
+
type: "text",
|
|
273
|
+
name: "fileName",
|
|
274
|
+
message: "File name (e.g. index.ts, .env.example):",
|
|
275
|
+
validate: (v) => v.trim().length > 0 ? true : "File name should not be empty"
|
|
276
|
+
});
|
|
277
|
+
if (fileName) {
|
|
278
|
+
const { content } = await (0, import_prompts.default)({
|
|
279
|
+
type: "text",
|
|
280
|
+
name: "content",
|
|
281
|
+
message: "File content (leave empty for blank file):"
|
|
282
|
+
});
|
|
283
|
+
current.node[fileName.trim()] = content?.trim() ?? null;
|
|
284
|
+
console.log(import_chalk3.default.green(` Added file: ${fileName.trim()}`));
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
if (action === "dir") {
|
|
288
|
+
const { dirName } = await (0, import_prompts.default)({
|
|
289
|
+
type: "text",
|
|
290
|
+
name: "dirName",
|
|
291
|
+
message: "Directory Name",
|
|
292
|
+
validate: (v) => v.trim().length > 0 ? true : "Directory name cannot be empty"
|
|
293
|
+
});
|
|
294
|
+
if (dirName) {
|
|
295
|
+
const newNode = {};
|
|
296
|
+
current.node[dirName.trim()] = newNode;
|
|
297
|
+
console.log(import_chalk3.default.blue(`Added directory: ${dirName.trim()}`));
|
|
298
|
+
queue.push({
|
|
299
|
+
node: newNode,
|
|
300
|
+
currentPath: import_path3.default.join(current.currentPath, dirName.trim()),
|
|
301
|
+
label: dirName.trim()
|
|
302
|
+
});
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
return root;
|
|
308
|
+
}
|
|
309
|
+
async function initCommand() {
|
|
310
|
+
try {
|
|
311
|
+
const structure = await buildInteractive();
|
|
312
|
+
console.log(import_chalk3.default.bold("\n Your blueprint:\n"));
|
|
313
|
+
console.log(import_chalk3.default.dim(JSON.stringify(structure, null, 2)));
|
|
314
|
+
const { outputFile } = await (0, import_prompts.default)({
|
|
315
|
+
type: "text",
|
|
316
|
+
name: "outputFile",
|
|
317
|
+
message: "Save blueprint as: ",
|
|
318
|
+
initial: "scaffold.json"
|
|
319
|
+
});
|
|
320
|
+
if (outputFile) {
|
|
321
|
+
const outputPath = import_path3.default.resolve(process.cwd(), outputFile);
|
|
322
|
+
await import_fs_extra3.default.writeFile(
|
|
323
|
+
outputPath,
|
|
324
|
+
JSON.stringify(structure, null, 2),
|
|
325
|
+
"utf-8"
|
|
326
|
+
);
|
|
327
|
+
console.log(
|
|
328
|
+
import_chalk3.default.green(`
|
|
329
|
+
Blueprint saved to: ${import_chalk3.default.bold(outputPath)}`)
|
|
330
|
+
);
|
|
331
|
+
console.log(
|
|
332
|
+
import_chalk3.default.dim(
|
|
333
|
+
`
|
|
334
|
+
Run: Scaffinity generate ${outputFile} to create your structure
|
|
335
|
+
`
|
|
336
|
+
)
|
|
337
|
+
);
|
|
338
|
+
}
|
|
339
|
+
} catch (error) {
|
|
340
|
+
console.error(import_chalk3.default.red("Init failed"), error);
|
|
341
|
+
process.exit(1);
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
// src/cli.ts
|
|
346
|
+
var program = new import_commander.Command();
|
|
347
|
+
console.log(
|
|
348
|
+
import_chalk4.default.bold.cyan("\n Scaffinity") + import_chalk4.default.dim(" - Project structure generator\n")
|
|
349
|
+
);
|
|
350
|
+
program.name("scaffinity").description(
|
|
351
|
+
"Generate, export, and share project structures as portable JSON blueprints"
|
|
352
|
+
).version("1.0.0");
|
|
353
|
+
program.command("generate <blueprint>").alias("g").description("Generate project structure from a JSON blueprint file").option(
|
|
354
|
+
"-o, --output <dir>",
|
|
355
|
+
"Output directory (default: current repository)"
|
|
356
|
+
).option("-d, --dry", "Dry run - preview without creating files").option("-v, --verbose", "Show each file/folder being created").action(
|
|
357
|
+
async (blueprint, options) => {
|
|
358
|
+
await generateCommand(blueprint, {
|
|
359
|
+
output: options.output,
|
|
360
|
+
dry: options.dry ?? false,
|
|
361
|
+
verbose: options.verbose ?? false
|
|
362
|
+
});
|
|
363
|
+
}
|
|
364
|
+
);
|
|
365
|
+
program.command("export <path>").alias("e").description("Export an existing project structure to a JSON blueprint").option("-o, --output <file>", "Save blueprint to file (default: stdout)").option("-i, --ignore <patterns...>", "Additional patterns to ignore").option("--depth <number>", "Maximum directory depth (default: 10)", parseInt).action(
|
|
366
|
+
async (projectPath, options) => {
|
|
367
|
+
await exportCommand(projectPath, {
|
|
368
|
+
output: options.output,
|
|
369
|
+
ignore: options.ignore,
|
|
370
|
+
depth: options.depth
|
|
371
|
+
});
|
|
372
|
+
}
|
|
373
|
+
);
|
|
374
|
+
program.command("init").alias("i").description("Interactively build a JSON blueprint step by step").action(async () => {
|
|
375
|
+
await initCommand();
|
|
376
|
+
});
|
|
377
|
+
program.addHelpText(
|
|
378
|
+
"after",
|
|
379
|
+
`
|
|
380
|
+
${import_chalk4.default.bold("Examples:")}
|
|
381
|
+
${import_chalk4.default.cyan("scaffinity generate blueprint.json")} Generate from blueprint
|
|
382
|
+
${import_chalk4.default.cyan("scaffinity generate blueprint.json -o ./app")} Generate into specific folder
|
|
383
|
+
${import_chalk4.default.cyan("scaffinity export ./my-project -o out.json")} Export existing project
|
|
384
|
+
${import_chalk4.default.cyan("scaffinity init")} Interactive builder
|
|
385
|
+
|
|
386
|
+
${import_chalk4.default.bold("Blueprint format:")}
|
|
387
|
+
${import_chalk4.default.dim("{")}
|
|
388
|
+
${import_chalk4.default.dim(' "src": {')}
|
|
389
|
+
${import_chalk4.default.dim(' "index.ts":"",')}
|
|
390
|
+
${import_chalk4.default.dim(' "utils":{')}
|
|
391
|
+
${import_chalk4.default.dim(' "helper.ts":"// Your content here"')}
|
|
392
|
+
${import_chalk4.default.dim(" }")}
|
|
393
|
+
${import_chalk4.default.dim(" },")}
|
|
394
|
+
${import_chalk4.default.dim(' "env.example":"PORT=3000\\nNODE_ENV=development"')}
|
|
395
|
+
${import_chalk4.default.dim("}")}
|
|
396
|
+
|
|
397
|
+
${import_chalk4.default.dim("Github: https://github.com/devi5040/scaffinity")}`
|
|
398
|
+
);
|
|
399
|
+
program.parse(process.argv);
|
|
400
|
+
if (!process.argv.slice(2).length) program.outputHelp;
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
type FileContent = string | {
|
|
2
|
+
template: string;
|
|
3
|
+
options?: Record<string, unknown>;
|
|
4
|
+
};
|
|
5
|
+
type StructureNode = {
|
|
6
|
+
[key: string]: StructureNode | FileContent | null;
|
|
7
|
+
};
|
|
8
|
+
interface GenerateOptions {
|
|
9
|
+
output?: string;
|
|
10
|
+
dry?: boolean;
|
|
11
|
+
verbose?: boolean;
|
|
12
|
+
}
|
|
13
|
+
interface ExportOptions {
|
|
14
|
+
output?: string;
|
|
15
|
+
ignore?: string[];
|
|
16
|
+
depth?: number;
|
|
17
|
+
}
|
|
18
|
+
interface ScaffoldResult {
|
|
19
|
+
created: string[];
|
|
20
|
+
skipped: string[];
|
|
21
|
+
errors: string[];
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
declare function generateCommand(jsonFile: string, options: GenerateOptions): Promise<void>;
|
|
25
|
+
|
|
26
|
+
declare function exportCommand(projectPath: string, options: ExportOptions): Promise<void>;
|
|
27
|
+
|
|
28
|
+
declare function initCommand(): Promise<void>;
|
|
29
|
+
|
|
30
|
+
export { type ExportOptions, type GenerateOptions, type ScaffoldResult, type StructureNode, exportCommand, generateCommand, initCommand };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,358 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __export = (target, all) => {
|
|
9
|
+
for (var name in all)
|
|
10
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
+
};
|
|
12
|
+
var __copyProps = (to, from, except, desc) => {
|
|
13
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
+
for (let key of __getOwnPropNames(from))
|
|
15
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
+
}
|
|
18
|
+
return to;
|
|
19
|
+
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
28
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
|
+
|
|
30
|
+
// src/index.ts
|
|
31
|
+
var index_exports = {};
|
|
32
|
+
__export(index_exports, {
|
|
33
|
+
exportCommand: () => exportCommand,
|
|
34
|
+
generateCommand: () => generateCommand,
|
|
35
|
+
initCommand: () => initCommand
|
|
36
|
+
});
|
|
37
|
+
module.exports = __toCommonJS(index_exports);
|
|
38
|
+
|
|
39
|
+
// src/commands/generate.ts
|
|
40
|
+
var import_path = __toESM(require("path"));
|
|
41
|
+
var import_chalk = __toESM(require("chalk"));
|
|
42
|
+
var import_fs_extra = __toESM(require("fs-extra"));
|
|
43
|
+
var import_ora = __toESM(require("ora"));
|
|
44
|
+
function isFileContent(value) {
|
|
45
|
+
return typeof value === "string" || value === null || typeof value === "object" && value != null && "template" in value;
|
|
46
|
+
}
|
|
47
|
+
function resolveContent(value) {
|
|
48
|
+
if (value === null || value === void 0) return "";
|
|
49
|
+
if (typeof value === "string") return value;
|
|
50
|
+
if (typeof value === "object" && "template" in value) {
|
|
51
|
+
const v = value;
|
|
52
|
+
return `//Generated from template: ${v.template}
|
|
53
|
+
// Options: ${JSON.stringify(v.options ?? {}, null, 2)}
|
|
54
|
+
`;
|
|
55
|
+
}
|
|
56
|
+
return "";
|
|
57
|
+
}
|
|
58
|
+
async function processNode(node, currentPath, result, options) {
|
|
59
|
+
for (const [key, value] of Object.entries(node)) {
|
|
60
|
+
const fullPath = import_path.default.join(currentPath, key);
|
|
61
|
+
if (isFileContent(value)) {
|
|
62
|
+
if (options.dry) {
|
|
63
|
+
result.created.push(fullPath);
|
|
64
|
+
if (options.verbose) {
|
|
65
|
+
console.log(
|
|
66
|
+
import_chalk.default.cyan(" [dry] create file:"),
|
|
67
|
+
import_chalk.default.white(fullPath)
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
} else {
|
|
71
|
+
try {
|
|
72
|
+
await import_fs_extra.default.ensureDir(import_path.default.dirname(fullPath));
|
|
73
|
+
if (await import_fs_extra.default.pathExists(fullPath)) {
|
|
74
|
+
result.skipped.push(fullPath);
|
|
75
|
+
if (options.verbose)
|
|
76
|
+
console.log(import_chalk.default.yellow(" skip:"), import_chalk.default.white(fullPath));
|
|
77
|
+
} else {
|
|
78
|
+
await import_fs_extra.default.writeFile(fullPath, resolveContent(value), "utf-8");
|
|
79
|
+
result.created.push(fullPath);
|
|
80
|
+
if (options.verbose)
|
|
81
|
+
console.log(import_chalk.default.green(" create:"), import_chalk.default.white(fullPath));
|
|
82
|
+
}
|
|
83
|
+
} catch (error) {
|
|
84
|
+
result.errors.push(fullPath);
|
|
85
|
+
console.log(import_chalk.default.red(" error:"), import_chalk.default.white(fullPath));
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
} else if (typeof value === "object" && value !== null) {
|
|
89
|
+
if (!options.dry) await import_fs_extra.default.ensureDir(fullPath);
|
|
90
|
+
if (options.verbose)
|
|
91
|
+
console.log(import_chalk.default.blue(" makedir:"), import_chalk.default.white(fullPath));
|
|
92
|
+
await processNode(value, fullPath, result, options);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
async function generateCommand(jsonFile, options) {
|
|
97
|
+
const spinner = (0, import_ora.default)({
|
|
98
|
+
text: "Reading blueprint...",
|
|
99
|
+
stream: process.stdout
|
|
100
|
+
}).start();
|
|
101
|
+
try {
|
|
102
|
+
const absoluteJson = import_path.default.resolve(process.cwd(), jsonFile);
|
|
103
|
+
if (!await import_fs_extra.default.pathExists(absoluteJson)) {
|
|
104
|
+
spinner.fail(import_chalk.default.red(`Blueprint file not found: ${jsonFile}`));
|
|
105
|
+
process.exit(1);
|
|
106
|
+
}
|
|
107
|
+
const raw = await import_fs_extra.default.readFile(absoluteJson, "utf-8");
|
|
108
|
+
let structre;
|
|
109
|
+
try {
|
|
110
|
+
structre = JSON.parse(raw);
|
|
111
|
+
} catch {
|
|
112
|
+
spinner.fail(import_chalk.default.red("Invalid Json in blueprint file"));
|
|
113
|
+
process.exit(1);
|
|
114
|
+
}
|
|
115
|
+
const outputDir = import_path.default.resolve(process.cwd(), options.output ?? ".");
|
|
116
|
+
await import_fs_extra.default.ensureDir(outputDir);
|
|
117
|
+
spinner.succeed("Blueprint loaded");
|
|
118
|
+
if (options.dry)
|
|
119
|
+
console.log(import_chalk.default.yellow("\nDry-run - no files will be created\n"));
|
|
120
|
+
if (options.verbose) console.log(import_chalk.default.dim("\nProcessing Structure:\n"));
|
|
121
|
+
const result = { created: [], skipped: [], errors: [] };
|
|
122
|
+
await processNode(structre, outputDir, result, options);
|
|
123
|
+
console.log("\n" + import_chalk.default.bold("Scaffinity summary"));
|
|
124
|
+
console.log(import_chalk.default.dim("-".repeat(40)));
|
|
125
|
+
console.log(
|
|
126
|
+
import_chalk.default.green(` \u2713 Created : ${result.created.length} files/dirs`)
|
|
127
|
+
);
|
|
128
|
+
if (result.skipped.length > 0)
|
|
129
|
+
console.log(
|
|
130
|
+
import_chalk.default.yellow(` \u26A0 Skipped : ${result.skipped.length} (already exist)`)
|
|
131
|
+
);
|
|
132
|
+
if (result.errors.length > 0)
|
|
133
|
+
console.log(import_chalk.default.red(` \u2717 Errors : ${result.errors.length}`));
|
|
134
|
+
console.log(import_chalk.default.dim("-".repeat(40)));
|
|
135
|
+
if (!options.dry && result.created.length > 0)
|
|
136
|
+
console.log(
|
|
137
|
+
import_chalk.default.green(
|
|
138
|
+
`
|
|
139
|
+
\u2705 Project structure generated at: ${import_chalk.default.bold(outputDir)}
|
|
140
|
+
`
|
|
141
|
+
)
|
|
142
|
+
);
|
|
143
|
+
} catch (error) {
|
|
144
|
+
spinner.fail(import_chalk.default.red("Generation failed"));
|
|
145
|
+
console.error(error);
|
|
146
|
+
process.exit(1);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// src/commands/export.ts
|
|
151
|
+
var import_path2 = __toESM(require("path"));
|
|
152
|
+
var import_fs_extra2 = __toESM(require("fs-extra"));
|
|
153
|
+
var import_chalk2 = __toESM(require("chalk"));
|
|
154
|
+
var import_ora2 = __toESM(require("ora"));
|
|
155
|
+
var DEFAULT_IGNORE = [
|
|
156
|
+
"node_modules",
|
|
157
|
+
".git",
|
|
158
|
+
"dist",
|
|
159
|
+
"build",
|
|
160
|
+
".next",
|
|
161
|
+
".nuxt",
|
|
162
|
+
"coverage",
|
|
163
|
+
".DS_Store",
|
|
164
|
+
"Thumbs.db",
|
|
165
|
+
"*.log",
|
|
166
|
+
".env",
|
|
167
|
+
".env.local"
|
|
168
|
+
];
|
|
169
|
+
function matchesIgnore(name, ignoreList) {
|
|
170
|
+
return ignoreList.some((pattern) => {
|
|
171
|
+
if (pattern.startsWith("*")) return name.endsWith(pattern.slice(1));
|
|
172
|
+
return name === pattern;
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
async function buildStructure(dirPath, ignoreList, currentDepth, maxDepth) {
|
|
176
|
+
const result = {};
|
|
177
|
+
if (currentDepth > maxDepth) return result;
|
|
178
|
+
const entries = await import_fs_extra2.default.readdir(dirPath, { withFileTypes: true });
|
|
179
|
+
for (const entry of entries) {
|
|
180
|
+
if (matchesIgnore(entry.name, ignoreList)) continue;
|
|
181
|
+
const fullPath = import_path2.default.join(dirPath, entry.name);
|
|
182
|
+
if (entry.isDirectory())
|
|
183
|
+
result[entry.name] = await buildStructure(
|
|
184
|
+
fullPath,
|
|
185
|
+
ignoreList,
|
|
186
|
+
currentDepth + 1,
|
|
187
|
+
maxDepth
|
|
188
|
+
);
|
|
189
|
+
else if (entry.isFile()) result[entry.name] = null;
|
|
190
|
+
}
|
|
191
|
+
return result;
|
|
192
|
+
}
|
|
193
|
+
async function exportCommand(projectPath, options) {
|
|
194
|
+
const spinner = (0, import_ora2.default)("Scanning project structure...").start();
|
|
195
|
+
try {
|
|
196
|
+
const absolutePath = import_path2.default.resolve(process.cwd(), projectPath);
|
|
197
|
+
if (!await import_fs_extra2.default.pathExists(absolutePath)) {
|
|
198
|
+
spinner.fail(import_chalk2.default.red(`Path not found: ${projectPath}`));
|
|
199
|
+
process.exit(1);
|
|
200
|
+
}
|
|
201
|
+
const stat = await import_fs_extra2.default.stat(absolutePath);
|
|
202
|
+
if (!stat.isDirectory()) {
|
|
203
|
+
spinner.fail(import_chalk2.default.red(`Path is not a directory: ${projectPath}`));
|
|
204
|
+
process.exit(1);
|
|
205
|
+
}
|
|
206
|
+
const ignoreList = [...DEFAULT_IGNORE, ...options.ignore ?? []];
|
|
207
|
+
const maxDepth = options.depth ?? 10;
|
|
208
|
+
const structure = await buildStructure(
|
|
209
|
+
absolutePath,
|
|
210
|
+
ignoreList,
|
|
211
|
+
0,
|
|
212
|
+
maxDepth
|
|
213
|
+
);
|
|
214
|
+
spinner.succeed("Structure scanned");
|
|
215
|
+
const json = JSON.stringify(structure, null, 2);
|
|
216
|
+
if (options.output) {
|
|
217
|
+
const outputPath = import_path2.default.resolve(process.cwd(), options.output);
|
|
218
|
+
await import_fs_extra2.default.writeFile(outputPath, json, "utf-8");
|
|
219
|
+
console.log(
|
|
220
|
+
import_chalk2.default.green(`
|
|
221
|
+
\u2705 Blueprint exported to: ${import_chalk2.default.bold(outputPath)}
|
|
222
|
+
`)
|
|
223
|
+
);
|
|
224
|
+
} else
|
|
225
|
+
console.log(json);
|
|
226
|
+
const fileCount = (json.match(/:null/g) ?? []).length;
|
|
227
|
+
const dirCount = Object.keys(structure).length;
|
|
228
|
+
if (options.output) {
|
|
229
|
+
console.log(
|
|
230
|
+
import_chalk2.default.dim(` Directories: ${dirCount} | Files: ${fileCount}`)
|
|
231
|
+
);
|
|
232
|
+
console.log(import_chalk2.default.dim(` Ignored: ${ignoreList.join(", ")}
|
|
233
|
+
`));
|
|
234
|
+
}
|
|
235
|
+
} catch (error) {
|
|
236
|
+
spinner.fail(import_chalk2.default.red("Export failed"));
|
|
237
|
+
console.error(error);
|
|
238
|
+
process.exit(1);
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
// src/commands/init.ts
|
|
243
|
+
var import_chalk3 = __toESM(require("chalk"));
|
|
244
|
+
var import_prompts = __toESM(require("prompts"));
|
|
245
|
+
var import_path3 = __toESM(require("path"));
|
|
246
|
+
var import_fs_extra3 = __toESM(require("fs-extra"));
|
|
247
|
+
async function buildInteractive() {
|
|
248
|
+
const root = {};
|
|
249
|
+
const queue = [{ node: root, currentPath: "/", label: "root" }];
|
|
250
|
+
console.log(import_chalk3.default.bold.cyan("\n Scaffinity Interactive Builder"));
|
|
251
|
+
console.log(import_chalk3.default.dim("Build your project structure step by step.\n"));
|
|
252
|
+
console.log(
|
|
253
|
+
import_chalk3.default.dim("Commands: add file -> type name with extension (e.g. index.ts)")
|
|
254
|
+
);
|
|
255
|
+
console.log(
|
|
256
|
+
import_chalk3.default.dim(" add dir -> type name without extension (e.g. src)")
|
|
257
|
+
);
|
|
258
|
+
console.log(import_chalk3.default.dim(" done -> finish current directory\n"));
|
|
259
|
+
while (queue.length > 0) {
|
|
260
|
+
const current = queue.shift();
|
|
261
|
+
console.log(import_chalk3.default.blue(`
|
|
262
|
+
${current?.label} (${current?.currentPath})`));
|
|
263
|
+
let continueAdding = true;
|
|
264
|
+
while (continueAdding) {
|
|
265
|
+
const { action } = await (0, import_prompts.default)({
|
|
266
|
+
type: "select",
|
|
267
|
+
name: "action",
|
|
268
|
+
message: `Add to ${import_chalk3.default.cyan(current.label)}:`,
|
|
269
|
+
choices: [
|
|
270
|
+
{ title: "Add file", value: "file" },
|
|
271
|
+
{ title: "Add directory", value: "dir" },
|
|
272
|
+
{ title: "Done with this directory", value: "done" }
|
|
273
|
+
]
|
|
274
|
+
});
|
|
275
|
+
if (!action || action === "done") {
|
|
276
|
+
continueAdding = false;
|
|
277
|
+
break;
|
|
278
|
+
}
|
|
279
|
+
if (action === "file") {
|
|
280
|
+
const { fileName } = await (0, import_prompts.default)({
|
|
281
|
+
type: "text",
|
|
282
|
+
name: "fileName",
|
|
283
|
+
message: "File name (e.g. index.ts, .env.example):",
|
|
284
|
+
validate: (v) => v.trim().length > 0 ? true : "File name should not be empty"
|
|
285
|
+
});
|
|
286
|
+
if (fileName) {
|
|
287
|
+
const { content } = await (0, import_prompts.default)({
|
|
288
|
+
type: "text",
|
|
289
|
+
name: "content",
|
|
290
|
+
message: "File content (leave empty for blank file):"
|
|
291
|
+
});
|
|
292
|
+
current.node[fileName.trim()] = content?.trim() ?? null;
|
|
293
|
+
console.log(import_chalk3.default.green(` Added file: ${fileName.trim()}`));
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
if (action === "dir") {
|
|
297
|
+
const { dirName } = await (0, import_prompts.default)({
|
|
298
|
+
type: "text",
|
|
299
|
+
name: "dirName",
|
|
300
|
+
message: "Directory Name",
|
|
301
|
+
validate: (v) => v.trim().length > 0 ? true : "Directory name cannot be empty"
|
|
302
|
+
});
|
|
303
|
+
if (dirName) {
|
|
304
|
+
const newNode = {};
|
|
305
|
+
current.node[dirName.trim()] = newNode;
|
|
306
|
+
console.log(import_chalk3.default.blue(`Added directory: ${dirName.trim()}`));
|
|
307
|
+
queue.push({
|
|
308
|
+
node: newNode,
|
|
309
|
+
currentPath: import_path3.default.join(current.currentPath, dirName.trim()),
|
|
310
|
+
label: dirName.trim()
|
|
311
|
+
});
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
return root;
|
|
317
|
+
}
|
|
318
|
+
async function initCommand() {
|
|
319
|
+
try {
|
|
320
|
+
const structure = await buildInteractive();
|
|
321
|
+
console.log(import_chalk3.default.bold("\n Your blueprint:\n"));
|
|
322
|
+
console.log(import_chalk3.default.dim(JSON.stringify(structure, null, 2)));
|
|
323
|
+
const { outputFile } = await (0, import_prompts.default)({
|
|
324
|
+
type: "text",
|
|
325
|
+
name: "outputFile",
|
|
326
|
+
message: "Save blueprint as: ",
|
|
327
|
+
initial: "scaffold.json"
|
|
328
|
+
});
|
|
329
|
+
if (outputFile) {
|
|
330
|
+
const outputPath = import_path3.default.resolve(process.cwd(), outputFile);
|
|
331
|
+
await import_fs_extra3.default.writeFile(
|
|
332
|
+
outputPath,
|
|
333
|
+
JSON.stringify(structure, null, 2),
|
|
334
|
+
"utf-8"
|
|
335
|
+
);
|
|
336
|
+
console.log(
|
|
337
|
+
import_chalk3.default.green(`
|
|
338
|
+
Blueprint saved to: ${import_chalk3.default.bold(outputPath)}`)
|
|
339
|
+
);
|
|
340
|
+
console.log(
|
|
341
|
+
import_chalk3.default.dim(
|
|
342
|
+
`
|
|
343
|
+
Run: Scaffinity generate ${outputFile} to create your structure
|
|
344
|
+
`
|
|
345
|
+
)
|
|
346
|
+
);
|
|
347
|
+
}
|
|
348
|
+
} catch (error) {
|
|
349
|
+
console.error(import_chalk3.default.red("Init failed"), error);
|
|
350
|
+
process.exit(1);
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
354
|
+
0 && (module.exports = {
|
|
355
|
+
exportCommand,
|
|
356
|
+
generateCommand,
|
|
357
|
+
initCommand
|
|
358
|
+
});
|
package/package.json
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "scaffinity",
|
|
3
|
+
"version": "1.0.1",
|
|
4
|
+
"description": "Generate, export, and share project structures as portable JSON blueprints",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"types": "dist/index.d.ts",
|
|
7
|
+
"bin": {
|
|
8
|
+
"scaffinity": "dist/cli.js"
|
|
9
|
+
},
|
|
10
|
+
"scripts": {
|
|
11
|
+
"build": "tsup src/cli.ts src/index.ts --format cjs --dts",
|
|
12
|
+
"dev": "ts-node src/cli.ts",
|
|
13
|
+
"start": "node dist/cli.js"
|
|
14
|
+
},
|
|
15
|
+
"keywords": [
|
|
16
|
+
"scaffinity",
|
|
17
|
+
"cli",
|
|
18
|
+
"boilerplate",
|
|
19
|
+
"project-structure",
|
|
20
|
+
"generator",
|
|
21
|
+
"typescript",
|
|
22
|
+
"blueprint"
|
|
23
|
+
],
|
|
24
|
+
"author": "Deviprasad Rai <dpraidola@gmail.com>",
|
|
25
|
+
"license": "MIT",
|
|
26
|
+
"dependencies": {
|
|
27
|
+
"chalk": "^5.6.2",
|
|
28
|
+
"commander": "^15.0.0",
|
|
29
|
+
"fs-extra": "^11.3.5",
|
|
30
|
+
"ora": "^9.4.1",
|
|
31
|
+
"prompts": "^2.4.2"
|
|
32
|
+
},
|
|
33
|
+
"devDependencies": {
|
|
34
|
+
"@types/node": "^26.0.1",
|
|
35
|
+
"@types/fs-extra": "^11.0.4",
|
|
36
|
+
"@types/prompts": "^2.4.9",
|
|
37
|
+
"ts-node": "^10.9.2",
|
|
38
|
+
"tsup": "^8.5.1",
|
|
39
|
+
"typescript": "^6.0.3"
|
|
40
|
+
},
|
|
41
|
+
"engines": {
|
|
42
|
+
"node": ">=18"
|
|
43
|
+
},
|
|
44
|
+
"files": [
|
|
45
|
+
"dist",
|
|
46
|
+
"README.md",
|
|
47
|
+
"LICENSE"
|
|
48
|
+
]
|
|
49
|
+
}
|