create-project-arch 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +58 -0
- package/dist/cli.js +232 -0
- package/dist/cli.test.js +8 -0
- package/package.json +29 -0
- package/templates/arch-ui/.arch/edges/decision_to_domain.json +4 -0
- package/templates/arch-ui/.arch/edges/milestone_to_task.json +4 -0
- package/templates/arch-ui/.arch/edges/task_to_decision.json +4 -0
- package/templates/arch-ui/.arch/edges/task_to_module.json +4 -0
- package/templates/arch-ui/.arch/graph.json +17 -0
- package/templates/arch-ui/.arch/nodes/decisions.json +4 -0
- package/templates/arch-ui/.arch/nodes/domains.json +4 -0
- package/templates/arch-ui/.arch/nodes/milestones.json +4 -0
- package/templates/arch-ui/.arch/nodes/modules.json +4 -0
- package/templates/arch-ui/.arch/nodes/tasks.json +4 -0
- package/templates/arch-ui/app/api/architecture/map/route.ts +13 -0
- package/templates/arch-ui/app/api/decisions/route.ts +23 -0
- package/templates/arch-ui/app/api/domain-docs/route.ts +89 -0
- package/templates/arch-ui/app/api/domains/route.ts +10 -0
- package/templates/arch-ui/app/api/graph/route.ts +16 -0
- package/templates/arch-ui/app/api/health/route.ts +44 -0
- package/templates/arch-ui/app/api/node-files/route.ts +173 -0
- package/templates/arch-ui/app/api/phases/route.ts +10 -0
- package/templates/arch-ui/app/api/route.ts +22 -0
- package/templates/arch-ui/app/api/search/route.ts +56 -0
- package/templates/arch-ui/app/api/task-doc/[taskId]/route.ts +60 -0
- package/templates/arch-ui/app/api/tasks/route.ts +36 -0
- package/templates/arch-ui/app/api/trace/file/route.ts +40 -0
- package/templates/arch-ui/app/api/trace/task/[taskId]/route.ts +12 -0
- package/templates/arch-ui/app/architecture/page.tsx +5 -0
- package/templates/arch-ui/app/globals.css +240 -0
- package/templates/arch-ui/app/health/page.tsx +48 -0
- package/templates/arch-ui/app/layout.tsx +19 -0
- package/templates/arch-ui/app/page.tsx +5 -0
- package/templates/arch-ui/app/work/page.tsx +265 -0
- package/templates/arch-ui/components/app-shell.tsx +171 -0
- package/templates/arch-ui/components/error-boundary.tsx +53 -0
- package/templates/arch-ui/components/graph/arch-node.tsx +77 -0
- package/templates/arch-ui/components/graph/build-graph-from-dataset.ts +196 -0
- package/templates/arch-ui/components/graph/build-initial-graph.ts +245 -0
- package/templates/arch-ui/components/graph/graph-context-menu.tsx +84 -0
- package/templates/arch-ui/components/graph/graph-doc-panel.tsx +46 -0
- package/templates/arch-ui/components/graph/graph-types.ts +82 -0
- package/templates/arch-ui/components/graph/use-auto-layout.ts +65 -0
- package/templates/arch-ui/components/graph/use-connection-validation.ts +62 -0
- package/templates/arch-ui/components/graph/use-flow-persistence.ts +48 -0
- package/templates/arch-ui/components/graph-canvas.tsx +670 -0
- package/templates/arch-ui/components/health-panel.tsx +49 -0
- package/templates/arch-ui/components/inspector-context.tsx +35 -0
- package/templates/arch-ui/components/inspector.tsx +895 -0
- package/templates/arch-ui/components/markdown-viewer.tsx +74 -0
- package/templates/arch-ui/components/sidebar.tsx +531 -0
- package/templates/arch-ui/components/topbar.tsx +187 -0
- package/templates/arch-ui/components/work-table.tsx +57 -0
- package/templates/arch-ui/components/workspace-context.tsx +274 -0
- package/templates/arch-ui/eslint.config.js +2 -0
- package/templates/arch-ui/global.d.ts +1 -0
- package/templates/arch-ui/lib/api.ts +93 -0
- package/templates/arch-ui/lib/arch-model.ts +113 -0
- package/templates/arch-ui/lib/graph-dataset.ts +756 -0
- package/templates/arch-ui/lib/graph-schema.ts +408 -0
- package/templates/arch-ui/lib/project-root.ts +52 -0
- package/templates/arch-ui/lib/types.ts +116 -0
- package/templates/arch-ui/next-env.d.ts +6 -0
- package/templates/arch-ui/next.config.js +17 -0
- package/templates/arch-ui/package.json +38 -0
- package/templates/arch-ui/postcss.config.mjs +6 -0
- package/templates/arch-ui/tailwind.config.ts +11 -0
- package/templates/arch-ui/tsconfig.json +21 -0
- package/templates/ui-package/eslint.config.mjs +4 -0
- package/templates/ui-package/package.json +26 -0
- package/templates/ui-package/src/accordion.tsx +10 -0
- package/templates/ui-package/src/badge.tsx +12 -0
- package/templates/ui-package/src/button.tsx +32 -0
- package/templates/ui-package/src/card.tsx +22 -0
- package/templates/ui-package/src/code.tsx +6 -0
- package/templates/ui-package/src/command.tsx +18 -0
- package/templates/ui-package/src/dialog.tsx +6 -0
- package/templates/ui-package/src/dropdown-menu.tsx +10 -0
- package/templates/ui-package/src/input.tsx +6 -0
- package/templates/ui-package/src/navigation-menu.tsx +6 -0
- package/templates/ui-package/src/scroll-area.tsx +6 -0
- package/templates/ui-package/src/select.tsx +6 -0
- package/templates/ui-package/src/separator.tsx +6 -0
- package/templates/ui-package/src/sheet.tsx +6 -0
- package/templates/ui-package/src/skeleton.tsx +6 -0
- package/templates/ui-package/src/table.tsx +26 -0
- package/templates/ui-package/src/tabs.tsx +14 -0
- package/templates/ui-package/src/toggle-group.tsx +10 -0
- package/templates/ui-package/src/utils.ts +3 -0
- package/templates/ui-package/tsconfig.json +10 -0
package/README.md
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# create-project-arch
|
|
2
|
+
|
|
3
|
+
A scaffolding tool designed to bootstrap a new Turborepo monorepo with `project-arch` integrated natively.
|
|
4
|
+
|
|
5
|
+
## Usage
|
|
6
|
+
|
|
7
|
+
You can use `npx` or `pnpx` to run the scaffolder directly:
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npx create-project-arch my-new-architecture
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
### Under the Hood
|
|
14
|
+
|
|
15
|
+
When executed, `create-project-arch`:
|
|
16
|
+
|
|
17
|
+
1. Runs `npx create-turbo` to generate the raw workspace.
|
|
18
|
+
2. Invokes `pa init` to seed an `arch-model/` directory.
|
|
19
|
+
3. Injects architecture applications (`apps/arch` and `packages/ui`) from internal templates.
|
|
20
|
+
4. Auto-wires the `package.json` configurations so `project-arch` commands are available out of the box.
|
|
21
|
+
|
|
22
|
+
## Options
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
create-project-arch <project-name> [options]
|
|
26
|
+
|
|
27
|
+
Options:
|
|
28
|
+
--template <name> template (default: "nextjs-turbo")
|
|
29
|
+
--apps <items> comma-separated apps (default: "web,docs")
|
|
30
|
+
--pm <name> package manager (default: "pnpm")
|
|
31
|
+
--with-ai create ai/indexing directory (default: false)
|
|
32
|
+
--with-docs-site create docs app (default: enabled)
|
|
33
|
+
--force allow scaffolding in a non-empty target directory
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## Developing the Architecture UI (Arch-UI)
|
|
37
|
+
The `arch-ui` Next.js application and `@repo/ui` packages are stored as static templates inside `templates/`. To iterate on them with full IDE support and hot-reloading, use the Sandbox DX flow provided in the monorepo root:
|
|
38
|
+
|
|
39
|
+
1. **Initialize the Sandbox:**
|
|
40
|
+
```bash
|
|
41
|
+
pnpm run sandbox:init
|
|
42
|
+
```
|
|
43
|
+
*This scaffolds `testProject` and runs `pnpm install` inside it.*
|
|
44
|
+
|
|
45
|
+
2. **Start the Dev Server:**
|
|
46
|
+
```bash
|
|
47
|
+
pnpm run sandbox:dev
|
|
48
|
+
```
|
|
49
|
+
*This boots up Next.js for the `arch-ui` inside the sandbox on port 4020.*
|
|
50
|
+
|
|
51
|
+
3. **Develop & Iterate:**
|
|
52
|
+
Open the files located in `testProject/apps/arch/` and `testProject/packages/ui/`. Your changes will hot-reload.
|
|
53
|
+
|
|
54
|
+
4. **Sync Changes Back:**
|
|
55
|
+
When you are satisfied with your UI changes, sync them back to the static `templates/` directory so they are included in the next CLI release:
|
|
56
|
+
```bash
|
|
57
|
+
pnpm run sandbox:sync
|
|
58
|
+
```
|
package/dist/cli.js
ADDED
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
4
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
5
|
+
};
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
const path_1 = __importDefault(require("path"));
|
|
8
|
+
const fs_extra_1 = __importDefault(require("fs-extra"));
|
|
9
|
+
const child_process_1 = require("child_process");
|
|
10
|
+
const commander_1 = require("commander");
|
|
11
|
+
function getTemplatesRoot() {
|
|
12
|
+
return path_1.default.resolve(__dirname, "../templates");
|
|
13
|
+
}
|
|
14
|
+
async function getProjectArchVersion() {
|
|
15
|
+
const projectArchEntry = require.resolve("project-arch");
|
|
16
|
+
const projectArchPkgPath = path_1.default.resolve(projectArchEntry, "../../../package.json");
|
|
17
|
+
const sourceProjectArchPkg = await fs_extra_1.default.readJSON(projectArchPkgPath);
|
|
18
|
+
return String(sourceProjectArchPkg.version ?? "1.0.0");
|
|
19
|
+
}
|
|
20
|
+
function normalizePathForPackageJson(value) {
|
|
21
|
+
return value.split(path_1.default.sep).join("/");
|
|
22
|
+
}
|
|
23
|
+
async function getProjectArchDependencySpec(targetDir, consumerDir) {
|
|
24
|
+
const localProjectArchPath = path_1.default.resolve(targetDir, "..", "packages", "project-arch");
|
|
25
|
+
const localProjectArchPkgPath = path_1.default.join(localProjectArchPath, "package.json");
|
|
26
|
+
if (await fs_extra_1.default.pathExists(localProjectArchPkgPath)) {
|
|
27
|
+
const localProjectArchPkg = await fs_extra_1.default.readJSON(localProjectArchPkgPath);
|
|
28
|
+
if (localProjectArchPkg.name === "project-arch") {
|
|
29
|
+
const relative = path_1.default.relative(consumerDir, localProjectArchPath);
|
|
30
|
+
return `link:${normalizePathForPackageJson(relative)}`;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
const projectArchVersion = await getProjectArchVersion();
|
|
34
|
+
return `^${projectArchVersion}`;
|
|
35
|
+
}
|
|
36
|
+
function runCreateTurbo(projectName, options) {
|
|
37
|
+
const args = [
|
|
38
|
+
"--yes",
|
|
39
|
+
"create-turbo@latest",
|
|
40
|
+
projectName,
|
|
41
|
+
"--package-manager",
|
|
42
|
+
options.pm,
|
|
43
|
+
"--skip-install",
|
|
44
|
+
];
|
|
45
|
+
const result = (0, child_process_1.spawnSync)("npx", args, {
|
|
46
|
+
cwd: process.cwd(),
|
|
47
|
+
stdio: "inherit",
|
|
48
|
+
});
|
|
49
|
+
if (result.error) {
|
|
50
|
+
throw result.error;
|
|
51
|
+
}
|
|
52
|
+
if (typeof result.status === "number" && result.status !== 0) {
|
|
53
|
+
process.exit(result.status);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
async function runPaInit(targetDir, options) {
|
|
57
|
+
const { runCli } = await import("project-arch/cli");
|
|
58
|
+
const argv = [
|
|
59
|
+
"node",
|
|
60
|
+
"pa",
|
|
61
|
+
"init",
|
|
62
|
+
"--template",
|
|
63
|
+
options.template,
|
|
64
|
+
"--apps",
|
|
65
|
+
options.apps,
|
|
66
|
+
"--pm",
|
|
67
|
+
options.pm,
|
|
68
|
+
];
|
|
69
|
+
if (options.withAi) {
|
|
70
|
+
argv.push("--with-ai");
|
|
71
|
+
}
|
|
72
|
+
if (options.withDocsSite) {
|
|
73
|
+
argv.push("--with-docs-site");
|
|
74
|
+
}
|
|
75
|
+
const previousCwd = process.cwd();
|
|
76
|
+
process.chdir(targetDir);
|
|
77
|
+
try {
|
|
78
|
+
await runCli(argv);
|
|
79
|
+
}
|
|
80
|
+
finally {
|
|
81
|
+
process.chdir(previousCwd);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
async function wireProjectArchUsage(targetDir) {
|
|
85
|
+
const rootPackageJsonPath = path_1.default.join(targetDir, "package.json");
|
|
86
|
+
if (!(await fs_extra_1.default.pathExists(rootPackageJsonPath))) {
|
|
87
|
+
throw new Error(`Missing generated package.json in ${targetDir}`);
|
|
88
|
+
}
|
|
89
|
+
const projectArchDependency = await getProjectArchDependencySpec(targetDir, targetDir);
|
|
90
|
+
const pkg = await fs_extra_1.default.readJSON(rootPackageJsonPath);
|
|
91
|
+
const nextPkg = {
|
|
92
|
+
...pkg,
|
|
93
|
+
scripts: {
|
|
94
|
+
...(pkg.scripts ?? {}),
|
|
95
|
+
pa: "pa",
|
|
96
|
+
"arch:check": "pa check",
|
|
97
|
+
"arch:report": "pa report",
|
|
98
|
+
},
|
|
99
|
+
devDependencies: {
|
|
100
|
+
...(pkg.devDependencies ?? {}),
|
|
101
|
+
"project-arch": projectArchDependency,
|
|
102
|
+
},
|
|
103
|
+
};
|
|
104
|
+
await fs_extra_1.default.writeJSON(rootPackageJsonPath, nextPkg, { spaces: 2 });
|
|
105
|
+
await fs_extra_1.default.appendFile(rootPackageJsonPath, "\n");
|
|
106
|
+
}
|
|
107
|
+
async function scaffoldArchitectureApps(targetDir) {
|
|
108
|
+
const templatesRoot = getTemplatesRoot();
|
|
109
|
+
const archUiTemplate = path_1.default.join(templatesRoot, "arch-ui");
|
|
110
|
+
const uiPackageTemplate = path_1.default.join(templatesRoot, "ui-package");
|
|
111
|
+
const appsRoot = path_1.default.join(targetDir, "apps");
|
|
112
|
+
const packagesRoot = path_1.default.join(targetDir, "packages");
|
|
113
|
+
const archUiTarget = path_1.default.join(appsRoot, "arch");
|
|
114
|
+
const uiPackageTarget = path_1.default.join(packagesRoot, "ui");
|
|
115
|
+
if (!(await fs_extra_1.default.pathExists(archUiTemplate))) {
|
|
116
|
+
throw new Error(`Missing architecture app templates at ${templatesRoot}`);
|
|
117
|
+
}
|
|
118
|
+
if (!(await fs_extra_1.default.pathExists(uiPackageTemplate))) {
|
|
119
|
+
throw new Error(`Missing ui package templates at ${templatesRoot}`);
|
|
120
|
+
}
|
|
121
|
+
await fs_extra_1.default.ensureDir(appsRoot);
|
|
122
|
+
await fs_extra_1.default.ensureDir(packagesRoot);
|
|
123
|
+
await fs_extra_1.default.copy(uiPackageTemplate, uiPackageTarget, { overwrite: true });
|
|
124
|
+
await fs_extra_1.default.copy(archUiTemplate, archUiTarget, { overwrite: true });
|
|
125
|
+
const projectArchDependency = await getProjectArchDependencySpec(targetDir, archUiTarget);
|
|
126
|
+
const archUiPkgPath = path_1.default.join(archUiTarget, "package.json");
|
|
127
|
+
const archUiPkg = await fs_extra_1.default.readJSON(archUiPkgPath);
|
|
128
|
+
const nextArchUiPkg = {
|
|
129
|
+
...archUiPkg,
|
|
130
|
+
dependencies: {
|
|
131
|
+
...(archUiPkg.dependencies ?? {}),
|
|
132
|
+
"project-arch": projectArchDependency,
|
|
133
|
+
},
|
|
134
|
+
};
|
|
135
|
+
await fs_extra_1.default.writeJSON(archUiPkgPath, nextArchUiPkg, { spaces: 2 });
|
|
136
|
+
await fs_extra_1.default.appendFile(archUiPkgPath, "\n");
|
|
137
|
+
}
|
|
138
|
+
async function upsertArchModulesInMap(targetDir) {
|
|
139
|
+
const modulesPath = path_1.default.join(targetDir, "arch-model", "modules.json");
|
|
140
|
+
if (!(await fs_extra_1.default.pathExists(modulesPath))) {
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
const existing = (await fs_extra_1.default.readJSON(modulesPath));
|
|
144
|
+
const currentModules = Array.isArray(existing.modules) ? existing.modules : [];
|
|
145
|
+
const byName = new Map();
|
|
146
|
+
for (const moduleDef of currentModules) {
|
|
147
|
+
if (!moduleDef || typeof moduleDef.name !== "string") {
|
|
148
|
+
continue;
|
|
149
|
+
}
|
|
150
|
+
byName.set(moduleDef.name, {
|
|
151
|
+
name: moduleDef.name,
|
|
152
|
+
type: typeof moduleDef.type === "string" ? moduleDef.type : "application",
|
|
153
|
+
description: typeof moduleDef.description === "string"
|
|
154
|
+
? moduleDef.description
|
|
155
|
+
: "Module in the monorepo.",
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
byName.set("apps/arch", {
|
|
159
|
+
name: "apps/arch",
|
|
160
|
+
type: "application",
|
|
161
|
+
description: "Architecture control surface UI.",
|
|
162
|
+
});
|
|
163
|
+
byName.delete("apps/arch-api");
|
|
164
|
+
const merged = [...byName.values()].sort((a, b) => a.name.localeCompare(b.name));
|
|
165
|
+
await fs_extra_1.default.writeJSON(modulesPath, { modules: merged }, { spaces: 2 });
|
|
166
|
+
await fs_extra_1.default.appendFile(modulesPath, "\n");
|
|
167
|
+
}
|
|
168
|
+
async function ensureTargetDir(targetDir, force) {
|
|
169
|
+
if (!(await fs_extra_1.default.pathExists(targetDir))) {
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
const entries = await fs_extra_1.default.readdir(targetDir);
|
|
173
|
+
if (entries.length === 0) {
|
|
174
|
+
await fs_extra_1.default.remove(targetDir);
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
177
|
+
if (!force) {
|
|
178
|
+
throw new Error(`Target directory '${targetDir}' is not empty. Use --force to overwrite.`);
|
|
179
|
+
}
|
|
180
|
+
await fs_extra_1.default.remove(targetDir);
|
|
181
|
+
}
|
|
182
|
+
async function main() {
|
|
183
|
+
const program = new commander_1.Command();
|
|
184
|
+
program
|
|
185
|
+
.name("create-project-arch")
|
|
186
|
+
.description("Create a project-arch monorepo")
|
|
187
|
+
.argument("<project-name>", "target directory name")
|
|
188
|
+
.option("--template <name>", "template", "nextjs-turbo")
|
|
189
|
+
.option("--apps <items>", "comma-separated apps", "web,docs")
|
|
190
|
+
.option("--pm <name>", "package manager", "pnpm")
|
|
191
|
+
.option("--with-ai", "create ai/indexing directory", false)
|
|
192
|
+
.option("--with-docs-site", "create docs app (default: enabled)", true)
|
|
193
|
+
.option("--force", "allow scaffolding in a non-empty target directory", false)
|
|
194
|
+
.action(async (projectName, options) => {
|
|
195
|
+
if (projectName === "." || projectName === "./") {
|
|
196
|
+
throw new Error("Refusing to scaffold into current directory. Provide a new project directory name.");
|
|
197
|
+
}
|
|
198
|
+
if (!/^[a-zA-Z0-9-_]+$/.test(projectName)) {
|
|
199
|
+
throw new Error("Invalid project name. Only alphanumeric characters, dashes, and underscores are allowed.");
|
|
200
|
+
}
|
|
201
|
+
const targetDir = path_1.default.resolve(process.cwd(), projectName);
|
|
202
|
+
await ensureTargetDir(targetDir, options.force);
|
|
203
|
+
runCreateTurbo(projectName, options);
|
|
204
|
+
// Ensure empty scaffolded packages have a .gitkeep so they are tracked by git
|
|
205
|
+
const emptyPackages = ["api", "config", "database", "types"];
|
|
206
|
+
for (const pkg of emptyPackages) {
|
|
207
|
+
const pkgDir = path_1.default.join(targetDir, "packages", pkg);
|
|
208
|
+
if (await fs_extra_1.default.pathExists(pkgDir)) {
|
|
209
|
+
const entries = await fs_extra_1.default.readdir(pkgDir);
|
|
210
|
+
if (entries.length === 0) {
|
|
211
|
+
await fs_extra_1.default.writeFile(path_1.default.join(pkgDir, ".gitkeep"), "");
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
await runPaInit(targetDir, options);
|
|
216
|
+
await scaffoldArchitectureApps(targetDir);
|
|
217
|
+
await upsertArchModulesInMap(targetDir);
|
|
218
|
+
await wireProjectArchUsage(targetDir);
|
|
219
|
+
const relativeTarget = path_1.default.relative(process.cwd(), targetDir) || ".";
|
|
220
|
+
console.log(`\nCreated project-arch repo at ${relativeTarget}`);
|
|
221
|
+
console.log("\nNext steps:");
|
|
222
|
+
console.log(` cd ${relativeTarget}`);
|
|
223
|
+
console.log(" pnpm install");
|
|
224
|
+
console.log(" pnpm arch:check");
|
|
225
|
+
});
|
|
226
|
+
await program.parseAsync(process.argv);
|
|
227
|
+
}
|
|
228
|
+
main().catch((error) => {
|
|
229
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
230
|
+
console.error(message);
|
|
231
|
+
process.exit(1);
|
|
232
|
+
});
|
package/dist/cli.test.js
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const vitest_1 = require("vitest");
|
|
4
|
+
(0, vitest_1.describe)("create-project-arch CLI", () => {
|
|
5
|
+
(0, vitest_1.it)("runs test suite successfully", () => {
|
|
6
|
+
(0, vitest_1.expect)(true).toBe(true);
|
|
7
|
+
});
|
|
8
|
+
});
|
package/package.json
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "create-project-arch",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Scaffold a project-arch monorepo",
|
|
5
|
+
"bin": {
|
|
6
|
+
"create-project-arch": "dist/cli.js"
|
|
7
|
+
},
|
|
8
|
+
"main": "dist/cli.js",
|
|
9
|
+
"files": [
|
|
10
|
+
"dist",
|
|
11
|
+
"templates"
|
|
12
|
+
],
|
|
13
|
+
"dependencies": {
|
|
14
|
+
"commander": "^12.1.0",
|
|
15
|
+
"fs-extra": "^11.3.0",
|
|
16
|
+
"project-arch": "^1.0.0"
|
|
17
|
+
},
|
|
18
|
+
"devDependencies": {
|
|
19
|
+
"@types/fs-extra": "^11.0.4",
|
|
20
|
+
"@types/node": "^22.13.8",
|
|
21
|
+
"typescript": "^5.8.2"
|
|
22
|
+
},
|
|
23
|
+
"scripts": {
|
|
24
|
+
"build": "tsc -p tsconfig.json",
|
|
25
|
+
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
26
|
+
"lint": "eslint \"src/**/*.ts\"",
|
|
27
|
+
"test": "vitest run"
|
|
28
|
+
}
|
|
29
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
{
|
|
2
|
+
"schemaVersion": "1.0",
|
|
3
|
+
"nodes": {
|
|
4
|
+
"domains": 0,
|
|
5
|
+
"decisions": 0,
|
|
6
|
+
"milestones": 0,
|
|
7
|
+
"tasks": 0,
|
|
8
|
+
"modules": 0
|
|
9
|
+
},
|
|
10
|
+
"edges": {
|
|
11
|
+
"task_to_decision": 0,
|
|
12
|
+
"task_to_module": 0,
|
|
13
|
+
"decision_to_domain": 0,
|
|
14
|
+
"milestone_to_task": 0
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { NextResponse } from "next/server";
|
|
2
|
+
import { graph } from "project-arch";
|
|
3
|
+
import { readArchitectureMap } from "../../../../lib/arch-model";
|
|
4
|
+
import { getProjectRoot } from "../../../../lib/project-root";
|
|
5
|
+
|
|
6
|
+
export const runtime = "nodejs";
|
|
7
|
+
|
|
8
|
+
export async function GET() {
|
|
9
|
+
const root = getProjectRoot();
|
|
10
|
+
process.env.PROJECT_ROOT = root;
|
|
11
|
+
await graph.graphBuild();
|
|
12
|
+
return NextResponse.json(await readArchitectureMap(root));
|
|
13
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { NextRequest, NextResponse } from "next/server";
|
|
2
|
+
import { decisions } from "project-arch";
|
|
3
|
+
import { getProjectRoot } from "../../../lib/project-root";
|
|
4
|
+
|
|
5
|
+
export const runtime = "nodejs";
|
|
6
|
+
|
|
7
|
+
export async function POST(request: NextRequest) {
|
|
8
|
+
const root = getProjectRoot();
|
|
9
|
+
const body = (await request.json()) as {
|
|
10
|
+
scope?: "project" | "phase" | "milestone";
|
|
11
|
+
phase?: string;
|
|
12
|
+
milestone?: string;
|
|
13
|
+
slug?: string;
|
|
14
|
+
title?: string;
|
|
15
|
+
};
|
|
16
|
+
if (!body.scope || !["project", "phase", "milestone"].includes(body.scope)) {
|
|
17
|
+
return NextResponse.json(
|
|
18
|
+
{ success: false, errors: ["scope must be one of: project, phase, milestone"] },
|
|
19
|
+
{ status: 400 },
|
|
20
|
+
);
|
|
21
|
+
}
|
|
22
|
+
return NextResponse.json(await decisions.decisionCreate({ ...body, cwd: root }));
|
|
23
|
+
}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { readdir } from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { NextResponse } from "next/server";
|
|
4
|
+
import { getProjectRoot, normalizePath } from "../../../lib/project-root";
|
|
5
|
+
|
|
6
|
+
export const runtime = "nodejs";
|
|
7
|
+
|
|
8
|
+
type DocScope = "arch-domains" | "arch-model" | "architecture" | "roadmap";
|
|
9
|
+
|
|
10
|
+
function titleCase(value: string): string {
|
|
11
|
+
return value
|
|
12
|
+
.split(/\s+/)
|
|
13
|
+
.filter(Boolean)
|
|
14
|
+
.map((part) => part[0]?.toUpperCase() + part.slice(1).toLowerCase())
|
|
15
|
+
.join(" ");
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function humanizeFileName(fileName: string): string {
|
|
19
|
+
const base = fileName.replace(/\.[a-z0-9]+$/i, "");
|
|
20
|
+
return titleCase(base.replace(/[_-]+/g, " "));
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
async function collectDocs(
|
|
24
|
+
root: string,
|
|
25
|
+
relativeDir: DocScope,
|
|
26
|
+
allowedExtensions: Set<string>,
|
|
27
|
+
maxDepth = 4,
|
|
28
|
+
): Promise<
|
|
29
|
+
Array<{
|
|
30
|
+
id: string;
|
|
31
|
+
scope: DocScope;
|
|
32
|
+
file: string;
|
|
33
|
+
path: string;
|
|
34
|
+
title: string;
|
|
35
|
+
}>
|
|
36
|
+
> {
|
|
37
|
+
const output: Array<{
|
|
38
|
+
id: string;
|
|
39
|
+
scope: DocScope;
|
|
40
|
+
file: string;
|
|
41
|
+
path: string;
|
|
42
|
+
title: string;
|
|
43
|
+
}> = [];
|
|
44
|
+
const baseDir = path.join(root, relativeDir);
|
|
45
|
+
|
|
46
|
+
async function walk(currentDir: string, depth: number): Promise<void> {
|
|
47
|
+
if (depth > maxDepth) return;
|
|
48
|
+
const entries = await readdir(currentDir, { withFileTypes: true });
|
|
49
|
+
for (const entry of entries) {
|
|
50
|
+
const fullPath = path.join(currentDir, entry.name);
|
|
51
|
+
if (entry.isDirectory()) {
|
|
52
|
+
await walk(fullPath, depth + 1);
|
|
53
|
+
continue;
|
|
54
|
+
}
|
|
55
|
+
const ext = path.extname(entry.name).toLowerCase();
|
|
56
|
+
if (!allowedExtensions.has(ext)) continue;
|
|
57
|
+
const relativePath = normalizePath(path.relative(root, fullPath));
|
|
58
|
+
output.push({
|
|
59
|
+
id: relativePath,
|
|
60
|
+
scope: relativeDir,
|
|
61
|
+
file: entry.name,
|
|
62
|
+
path: relativePath,
|
|
63
|
+
title: humanizeFileName(entry.name),
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
try {
|
|
69
|
+
await walk(baseDir, 0);
|
|
70
|
+
} catch {
|
|
71
|
+
return [];
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return output.sort((a, b) => a.path.localeCompare(b.path));
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export async function GET() {
|
|
78
|
+
const root = getProjectRoot();
|
|
79
|
+
const [domainDocs, modelDocs, architectureDocs, roadmapDocs] = await Promise.all([
|
|
80
|
+
collectDocs(root, "arch-domains", new Set([".md", ".json"]), 2),
|
|
81
|
+
collectDocs(root, "arch-model", new Set([".md", ".json"]), 2),
|
|
82
|
+
collectDocs(root, "architecture", new Set([".md", ".json"]), 6),
|
|
83
|
+
collectDocs(root, "roadmap", new Set([".md", ".json"]), 8),
|
|
84
|
+
]);
|
|
85
|
+
|
|
86
|
+
return NextResponse.json({
|
|
87
|
+
docs: [...domainDocs, ...modelDocs, ...architectureDocs, ...roadmapDocs],
|
|
88
|
+
});
|
|
89
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { NextResponse } from "next/server";
|
|
2
|
+
import { readDomains } from "../../../lib/arch-model";
|
|
3
|
+
import { getProjectRoot } from "../../../lib/project-root";
|
|
4
|
+
|
|
5
|
+
export const runtime = "nodejs";
|
|
6
|
+
|
|
7
|
+
export async function GET() {
|
|
8
|
+
const root = getProjectRoot();
|
|
9
|
+
return NextResponse.json(await readDomains(root));
|
|
10
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { NextResponse } from "next/server";
|
|
2
|
+
import { graph } from "project-arch";
|
|
3
|
+
import { getProjectRoot } from "../../../lib/project-root";
|
|
4
|
+
import { readArchitectureMap } from "../../../lib/arch-model";
|
|
5
|
+
import { buildValidatedGraphDataset } from "../../../lib/graph-dataset";
|
|
6
|
+
|
|
7
|
+
export const runtime = "nodejs";
|
|
8
|
+
|
|
9
|
+
export async function GET() {
|
|
10
|
+
const root = getProjectRoot();
|
|
11
|
+
process.env.PROJECT_ROOT = root;
|
|
12
|
+
await graph.graphBuild();
|
|
13
|
+
const architectureMap = await readArchitectureMap(root);
|
|
14
|
+
const result = await buildValidatedGraphDataset(root, architectureMap);
|
|
15
|
+
return NextResponse.json(result);
|
|
16
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { NextResponse } from "next/server";
|
|
2
|
+
import { check } from "project-arch";
|
|
3
|
+
import { getProjectRoot } from "../../../lib/project-root";
|
|
4
|
+
import { readArchitectureMap } from "../../../lib/arch-model";
|
|
5
|
+
import { buildValidatedGraphDataset } from "../../../lib/graph-dataset";
|
|
6
|
+
|
|
7
|
+
export const runtime = "nodejs";
|
|
8
|
+
|
|
9
|
+
export async function GET() {
|
|
10
|
+
const root = getProjectRoot();
|
|
11
|
+
process.env.PROJECT_ROOT = root;
|
|
12
|
+
const [checkResult, architectureMap] = await Promise.all([check.checkRun(), readArchitectureMap(root)]);
|
|
13
|
+
const { validation } = await buildValidatedGraphDataset(root, architectureMap);
|
|
14
|
+
|
|
15
|
+
const graphErrors = validation.errors.map(
|
|
16
|
+
(issue) => `[graph:${issue.ruleId}] ${issue.message}`,
|
|
17
|
+
);
|
|
18
|
+
const graphWarnings = validation.warnings.map(
|
|
19
|
+
(issue) => `[graph:${issue.ruleId}] ${issue.message}`,
|
|
20
|
+
);
|
|
21
|
+
|
|
22
|
+
const maybeWrapped = checkResult as {
|
|
23
|
+
success?: boolean;
|
|
24
|
+
data?: { ok: boolean; errors: string[]; warnings: string[] };
|
|
25
|
+
errors?: string[];
|
|
26
|
+
};
|
|
27
|
+
const base =
|
|
28
|
+
maybeWrapped.data ??
|
|
29
|
+
({
|
|
30
|
+
ok: true,
|
|
31
|
+
errors: maybeWrapped.errors ?? [],
|
|
32
|
+
warnings: [],
|
|
33
|
+
} as { ok: boolean; errors: string[]; warnings: string[] });
|
|
34
|
+
|
|
35
|
+
return NextResponse.json({
|
|
36
|
+
success: maybeWrapped.success ?? true,
|
|
37
|
+
data: {
|
|
38
|
+
...base,
|
|
39
|
+
ok: base.ok && graphErrors.length === 0,
|
|
40
|
+
errors: [...base.errors, ...graphErrors],
|
|
41
|
+
warnings: [...base.warnings, ...graphWarnings],
|
|
42
|
+
},
|
|
43
|
+
});
|
|
44
|
+
}
|