@stackwright-pro/launch-stackwright-pro 0.4.0-alpha.15 → 0.4.0-alpha.151
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 +11 -20
- package/dist/index.js +278 -125
- package/dist/index.js.map +1 -1
- package/package.json +17 -17
- package/templates/pro/_app.tsx +14 -4
- package/templates/pro/app/slug-page/page.tsx +32 -0
- package/templates/pro/content.yml +1 -1
- package/templates/pro/next.config.js +3 -0
- package/templates/pro/prebuild.js +23 -95
- package/templates/pro/pro-providers.tsx +46 -0
- package/templates/pro/scripts/pro-content-plugin.js +103 -0
- package/templates/pro/scripts/start-mocks.js +145 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
PROPRIETARY SOFTWARE LICENSE
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024-2026 Per Aspera LLC. All Rights Reserved.
|
|
4
|
+
|
|
5
|
+
This software and associated documentation files (the "Software") are the
|
|
6
|
+
proprietary and confidential property of Per Aspera LLC ("Company").
|
|
7
|
+
|
|
8
|
+
RESTRICTIONS: You may not use, copy, modify, merge, publish, distribute,
|
|
9
|
+
sublicense, sell, or otherwise exploit this Software or any portion thereof
|
|
10
|
+
without the express prior written consent of the Company.
|
|
11
|
+
|
|
12
|
+
GOVERNMENT USE: Use, duplication, or disclosure by the U.S. Government is
|
|
13
|
+
subject to restrictions as set forth in FAR 52.227-19 (Commercial Computer
|
|
14
|
+
Software - Restricted Rights) and DFARS 252.227-7013 (Rights in Technical
|
|
15
|
+
Data and Computer Software), as applicable.
|
|
16
|
+
|
|
17
|
+
THE SOFTWARE IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
18
|
+
IMPLIED. IN NO EVENT SHALL THE COMPANY BE LIABLE FOR ANY CLAIM, DAMAGES, OR
|
|
19
|
+
OTHER LIABILITY ARISING FROM THE USE OF THE SOFTWARE.
|
|
20
|
+
|
|
21
|
+
For licensing inquiries: legal@peraspera.com
|
package/README.md
CHANGED
|
@@ -21,10 +21,14 @@ to automatically wire up Pro features:
|
|
|
21
21
|
## Quick Start
|
|
22
22
|
|
|
23
23
|
```bash
|
|
24
|
-
|
|
24
|
+
# Works — project created in $cwd/my-app
|
|
25
|
+
pnpx @stackwright-pro/launch-stackwright-pro --name my-app -y
|
|
25
26
|
# Dependencies install automatically (~7s)
|
|
26
27
|
cd my-app
|
|
27
|
-
|
|
28
|
+
npx @stackwright-pro/raft # Start the otter raft
|
|
29
|
+
|
|
30
|
+
# Equivalent explicit form
|
|
31
|
+
pnpx @stackwright-pro/launch-stackwright-pro my-app --name my-app -y
|
|
28
32
|
```
|
|
29
33
|
|
|
30
34
|
### With an OpenAPI Spec
|
|
@@ -72,7 +76,7 @@ Options:
|
|
|
72
76
|
--force Overwrite existing directory
|
|
73
77
|
--skip-otters Skip otter raft setup
|
|
74
78
|
-y, --yes Skip prompts, use defaults
|
|
75
|
-
--spec <
|
|
79
|
+
--spec <paths...> Paths to OpenAPI specs (can be specified multiple times)
|
|
76
80
|
--spec-name <name> Name for the API integration (default: derived from filename)
|
|
77
81
|
-V, --version Output the version number
|
|
78
82
|
-h, --help Display help
|
|
@@ -120,24 +124,11 @@ for architecture details, auth deep-dives, and deployment guides.
|
|
|
120
124
|
|
|
121
125
|
## Starting the Otter Raft
|
|
122
126
|
|
|
123
|
-
The otter raft runs in two modes:
|
|
124
|
-
|
|
125
|
-
### Interactive Mode (Default)
|
|
126
|
-
|
|
127
127
|
```bash
|
|
128
|
-
|
|
128
|
+
cd my-app
|
|
129
|
+
npx @stackwright-pro/raft
|
|
129
130
|
```
|
|
130
131
|
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
- Asks questions about your project via TUI
|
|
134
|
-
- Coordinates specialist otters (brand, theme, api, auth, page)
|
|
135
|
-
- Uses certificate pinning to ensure only approved otters are invoked
|
|
136
|
-
|
|
137
|
-
### Phases Mode (Debugging)
|
|
138
|
-
|
|
139
|
-
```bash
|
|
140
|
-
python -m stackwright_pro.raft.cli_adapter foreman --phases
|
|
141
|
-
```
|
|
132
|
+
The raft verifies otter integrity, loads project context from `.stackwright/init-context.json`, and spawns code-puppy in foreman mode. Tell the foreman about your specs in natural language — it coordinates the API, auth, data, and page otters to build your app.
|
|
142
133
|
|
|
143
|
-
|
|
134
|
+
All state lives in `.stackwright/` — you can interrupt and resume at any time.
|
package/dist/index.js
CHANGED
|
@@ -31,9 +31,9 @@ var require_package = __commonJS({
|
|
|
31
31
|
"package.json"(exports2, module2) {
|
|
32
32
|
module2.exports = {
|
|
33
33
|
name: "@stackwright-pro/launch-stackwright-pro",
|
|
34
|
-
version: "0.4.0-alpha.
|
|
34
|
+
version: "0.4.0-alpha.151",
|
|
35
35
|
description: "Launch a new Stackwright Pro project with OpenAPI integration, auth, and the otter raft",
|
|
36
|
-
license: "
|
|
36
|
+
license: "SEE LICENSE IN LICENSE",
|
|
37
37
|
publishConfig: {
|
|
38
38
|
access: "public"
|
|
39
39
|
},
|
|
@@ -69,21 +69,21 @@ var require_package = __commonJS({
|
|
|
69
69
|
"@stackwright-pro/mcp": "workspace:*",
|
|
70
70
|
"@stackwright-pro/openapi": "workspace:*",
|
|
71
71
|
"@stackwright-pro/otters": "workspace:*",
|
|
72
|
-
"@stackwright-pro/scaffold-hooks": "
|
|
73
|
-
"@stackwright/cli": "^0.
|
|
74
|
-
"@stackwright/scaffold-core": "^0.
|
|
72
|
+
"@stackwright-pro/scaffold-hooks": "workspace:*",
|
|
73
|
+
"@stackwright/cli": "^0.9.0",
|
|
74
|
+
"@stackwright/scaffold-core": "^0.3.3",
|
|
75
75
|
chalk: "^5.6.2",
|
|
76
|
-
commander: "^
|
|
77
|
-
"fs-extra": "^11.3",
|
|
78
|
-
"js-yaml": "^4.
|
|
76
|
+
commander: "^15.0.0",
|
|
77
|
+
"fs-extra": "^11.3.5",
|
|
78
|
+
"js-yaml": "^4.2.0"
|
|
79
79
|
},
|
|
80
80
|
devDependencies: {
|
|
81
81
|
"@types/fs-extra": "^11.0",
|
|
82
82
|
"@types/js-yaml": "^4.0.9",
|
|
83
|
-
"@types/node": "
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
vitest: "
|
|
83
|
+
"@types/node": "catalog:",
|
|
84
|
+
tsup: "catalog:",
|
|
85
|
+
typescript: "catalog:",
|
|
86
|
+
vitest: "catalog:"
|
|
87
87
|
}
|
|
88
88
|
};
|
|
89
89
|
}
|
|
@@ -91,89 +91,93 @@ var require_package = __commonJS({
|
|
|
91
91
|
|
|
92
92
|
// src/index.ts
|
|
93
93
|
var import_commander = require("commander");
|
|
94
|
-
var
|
|
94
|
+
var import_path2 = __toESM(require("path"));
|
|
95
95
|
var import_os = __toESM(require("os"));
|
|
96
|
-
var
|
|
96
|
+
var import_fs_extra2 = __toESM(require("fs-extra"));
|
|
97
97
|
var import_chalk = __toESM(require("chalk"));
|
|
98
|
-
var import_js_yaml = __toESM(require("js-yaml"));
|
|
99
98
|
var import_child_process = require("child_process");
|
|
100
99
|
var import_cli = require("@stackwright/cli");
|
|
101
100
|
var import_scaffold_core = require("@stackwright/scaffold-core");
|
|
102
101
|
var import_scaffold_hooks = require("@stackwright-pro/scaffold-hooks");
|
|
103
102
|
|
|
103
|
+
// src/scaffold.ts
|
|
104
|
+
var import_fs_extra = __toESM(require("fs-extra"));
|
|
105
|
+
var import_js_yaml = __toESM(require("js-yaml"));
|
|
106
|
+
var import_path = __toESM(require("path"));
|
|
107
|
+
|
|
104
108
|
// src/generated/workspace-versions.ts
|
|
105
109
|
var PRO_VERSIONS = {
|
|
106
|
-
"@stackwright-pro/auth": "0.
|
|
107
|
-
"@stackwright-pro/auth-nextjs": "0.
|
|
108
|
-
"@stackwright-pro/
|
|
109
|
-
"@stackwright-pro/
|
|
110
|
-
"@stackwright-pro/
|
|
110
|
+
"@stackwright-pro/auth": "0.2.0-alpha.24",
|
|
111
|
+
"@stackwright-pro/auth-nextjs": "0.3.0-alpha.13",
|
|
112
|
+
"@stackwright-pro/build-scripts-plugins": "0.1.0-alpha.14",
|
|
113
|
+
"@stackwright-pro/cesium": "0.1.0-alpha.2",
|
|
114
|
+
"@stackwright-pro/display-components": "0.2.0-alpha.11",
|
|
115
|
+
"@stackwright-pro/mcp": "0.2.0-alpha.112",
|
|
116
|
+
"@stackwright-pro/openapi": "0.3.0-alpha.39",
|
|
117
|
+
"@stackwright-pro/otters": "1.0.0-alpha.76",
|
|
118
|
+
"@stackwright-pro/pulse": "0.3.0-alpha.36",
|
|
119
|
+
"@stackwright-pro/workflow": "0.1.0-alpha.27",
|
|
120
|
+
"@stackwright-pro/workflow-components": "0.1.0-alpha.29"
|
|
111
121
|
};
|
|
112
122
|
var OSS_VERSIONS = {
|
|
113
|
-
"@stackwright/build-scripts": "^0.
|
|
114
|
-
"@stackwright/
|
|
115
|
-
"@stackwright/
|
|
116
|
-
"@stackwright/
|
|
117
|
-
"@stackwright/
|
|
118
|
-
"@stackwright/
|
|
119
|
-
"@stackwright/
|
|
123
|
+
"@stackwright/build-scripts": "^0.10.0",
|
|
124
|
+
"@stackwright/collections": "^0.1.2",
|
|
125
|
+
"@stackwright/core": "^0.10.1",
|
|
126
|
+
"@stackwright/icons": "^0.5.2",
|
|
127
|
+
"@stackwright/mcp": "^0.5.0",
|
|
128
|
+
"@stackwright/nextjs": "^0.6.0",
|
|
129
|
+
"@stackwright/otters": "^0.2.2",
|
|
130
|
+
"@stackwright/types": "^1.7.0",
|
|
131
|
+
"@stackwright/ui-shadcn": "^0.1.3"
|
|
120
132
|
};
|
|
121
133
|
|
|
122
|
-
// src/
|
|
123
|
-
(0, import_scaffold_hooks.registerProScaffoldHooks)();
|
|
124
|
-
var { version } = require_package();
|
|
125
|
-
async function copyProTemplate(templateName, destPath) {
|
|
126
|
-
const src = import_path.default.resolve(__dirname, "..", "templates", "pro", templateName);
|
|
127
|
-
await import_fs_extra.default.copy(src, destPath);
|
|
128
|
-
}
|
|
129
|
-
async function addAuthToStackwrightYml(targetDir) {
|
|
130
|
-
const ymlPath = import_path.default.join(targetDir, "stackwright.yml");
|
|
131
|
-
const content = await import_fs_extra.default.readFile(ymlPath, "utf-8");
|
|
132
|
-
const config = import_js_yaml.default.load(content, { schema: import_js_yaml.default.CORE_SCHEMA });
|
|
133
|
-
config.auth = {
|
|
134
|
-
type: "pki",
|
|
135
|
-
profile: "dod_cac",
|
|
136
|
-
source: "gateway_headers",
|
|
137
|
-
roles: [
|
|
138
|
-
{ name: "ADMIN", permissions: ["*"] },
|
|
139
|
-
{ name: "ANALYST", permissions: ["data:read", "data:write"] },
|
|
140
|
-
{ name: "VIEWER", permissions: ["data:read"] }
|
|
141
|
-
],
|
|
142
|
-
protected_routes: [{ path: "/*", roles: ["VIEWER", "ANALYST", "ADMIN"] }],
|
|
143
|
-
public_routes: ["/", "/getting-started"]
|
|
144
|
-
};
|
|
145
|
-
await import_fs_extra.default.writeFile(ymlPath, import_js_yaml.default.dump(config, { lineWidth: 120 }));
|
|
146
|
-
}
|
|
134
|
+
// src/scaffold.ts
|
|
147
135
|
async function addProDepsToPackageJson(targetDir) {
|
|
148
136
|
const pkgPath = import_path.default.join(targetDir, "package.json");
|
|
149
137
|
const content = await import_fs_extra.default.readFile(pkgPath, "utf-8");
|
|
150
138
|
const pkg = JSON.parse(content);
|
|
151
|
-
const dependencies = pkg.dependencies
|
|
152
|
-
const devDependencies = pkg.devDependencies
|
|
153
|
-
const scripts = pkg.scripts
|
|
139
|
+
const dependencies = typeof pkg.dependencies === "object" && pkg.dependencies !== null ? { ...pkg.dependencies } : {};
|
|
140
|
+
const devDependencies = typeof pkg.devDependencies === "object" && pkg.devDependencies !== null ? { ...pkg.devDependencies } : {};
|
|
141
|
+
const scripts = typeof pkg.scripts === "object" && pkg.scripts !== null ? { ...pkg.scripts } : {};
|
|
154
142
|
for (const [pkgName, pinnedVersion] of Object.entries(OSS_VERSIONS)) {
|
|
155
|
-
if (
|
|
143
|
+
if (pkgName in dependencies) {
|
|
156
144
|
dependencies[pkgName] = pinnedVersion;
|
|
157
145
|
}
|
|
158
|
-
if (
|
|
146
|
+
if (pkgName in devDependencies) {
|
|
159
147
|
devDependencies[pkgName] = pinnedVersion;
|
|
160
148
|
}
|
|
161
149
|
}
|
|
162
150
|
Object.assign(dependencies, {
|
|
163
|
-
"@stackwright-pro/mcp": PRO_VERSIONS["@stackwright-pro/mcp"],
|
|
164
|
-
"@stackwright-pro/otters": PRO_VERSIONS["@stackwright-pro/otters"],
|
|
165
|
-
"@stackwright-pro/openapi": PRO_VERSIONS["@stackwright-pro/openapi"],
|
|
166
151
|
"@stackwright-pro/auth": PRO_VERSIONS["@stackwright-pro/auth"],
|
|
167
152
|
"@stackwright-pro/auth-nextjs": PRO_VERSIONS["@stackwright-pro/auth-nextjs"],
|
|
168
|
-
|
|
153
|
+
"@stackwright-pro/build-scripts-plugins": PRO_VERSIONS["@stackwright-pro/build-scripts-plugins"],
|
|
154
|
+
// SBOM compliance — every Pro raft is a compliance product (swp-ryqz)
|
|
155
|
+
"@stackwright-pro/sbom-enterprise": "latest",
|
|
156
|
+
"@stackwright/sbom-generator": "^0.2.2",
|
|
157
|
+
// Cesium is the Pro default map provider (PR #368). The providers.tsx emitter
|
|
158
|
+
// unconditionally imports @stackwright-pro/cesium — both it and its peer dep
|
|
159
|
+
// must be present or next dev blows up on every fresh scaffold.
|
|
160
|
+
"@stackwright-pro/cesium": PRO_VERSIONS["@stackwright-pro/cesium"],
|
|
161
|
+
cesium: "^1.122.0",
|
|
162
|
+
"@stackwright-pro/display-components": PRO_VERSIONS["@stackwright-pro/display-components"],
|
|
163
|
+
"@stackwright-pro/mcp": PRO_VERSIONS["@stackwright-pro/mcp"],
|
|
164
|
+
"@stackwright-pro/openapi": PRO_VERSIONS["@stackwright-pro/openapi"],
|
|
165
|
+
"@stackwright-pro/otters": PRO_VERSIONS["@stackwright-pro/otters"],
|
|
166
|
+
"@stackwright-pro/pulse": PRO_VERSIONS["@stackwright-pro/pulse"],
|
|
167
|
+
"@stackwright-pro/workflow": PRO_VERSIONS["@stackwright-pro/workflow"],
|
|
168
|
+
"@stackwright-pro/workflow-components": PRO_VERSIONS["@stackwright-pro/workflow-components"],
|
|
169
|
+
"@stackwright/build-scripts": OSS_VERSIONS["@stackwright/build-scripts"],
|
|
170
|
+
zod: "^4.0.0"
|
|
169
171
|
});
|
|
170
172
|
Object.assign(devDependencies, {
|
|
171
|
-
"@stoplight/prism-cli": "^5.
|
|
173
|
+
"@stoplight/prism-cli": "^5.15.10",
|
|
174
|
+
concurrently: "^9.0.0"
|
|
172
175
|
});
|
|
173
176
|
Object.assign(scripts, {
|
|
174
|
-
|
|
175
|
-
"dev:
|
|
176
|
-
"dev:
|
|
177
|
+
mock: "node scripts/start-mocks.js",
|
|
178
|
+
"dev:admin": 'concurrently "pnpm mock" "NEXT_PUBLIC_MOCK_BACKEND=true MOCK_USER=admin next dev"',
|
|
179
|
+
"dev:analyst": 'concurrently "pnpm mock" "NEXT_PUBLIC_MOCK_BACKEND=true MOCK_USER=analyst next dev"',
|
|
180
|
+
"dev:viewer": 'concurrently "pnpm mock" "NEXT_PUBLIC_MOCK_BACKEND=true MOCK_USER=viewer next dev"',
|
|
177
181
|
prebuild: "node scripts/prebuild.js",
|
|
178
182
|
predev: "node scripts/prebuild.js"
|
|
179
183
|
});
|
|
@@ -182,6 +186,13 @@ async function addProDepsToPackageJson(targetDir) {
|
|
|
182
186
|
pkg.scripts = scripts;
|
|
183
187
|
await import_fs_extra.default.writeFile(pkgPath, JSON.stringify(pkg, null, 2) + "\n", "utf-8");
|
|
184
188
|
}
|
|
189
|
+
async function addAuthToStackwrightYml(targetDir) {
|
|
190
|
+
const ymlPath = import_path.default.join(targetDir, "stackwright.yml");
|
|
191
|
+
const content = await import_fs_extra.default.readFile(ymlPath, "utf-8");
|
|
192
|
+
const config = import_js_yaml.default.load(content, { schema: import_js_yaml.default.CORE_SCHEMA });
|
|
193
|
+
config.auth = {};
|
|
194
|
+
await import_fs_extra.default.writeFile(ymlPath, import_js_yaml.default.dump(config, { lineWidth: 120 }));
|
|
195
|
+
}
|
|
185
196
|
async function addIntegrationToStackwrightYml(targetDir, specName, specFilename, mockUrl) {
|
|
186
197
|
const ymlPath = import_path.default.join(targetDir, "stackwright.yml");
|
|
187
198
|
const content = await import_fs_extra.default.readFile(ymlPath, "utf-8");
|
|
@@ -195,20 +206,100 @@ async function addIntegrationToStackwrightYml(targetDir, specName, specFilename,
|
|
|
195
206
|
if (mockUrl) {
|
|
196
207
|
integration.mockUrl = mockUrl;
|
|
197
208
|
}
|
|
198
|
-
config.integrations = [
|
|
209
|
+
config.integrations = [
|
|
210
|
+
...Array.isArray(config.integrations) ? config.integrations : [],
|
|
211
|
+
integration
|
|
212
|
+
];
|
|
199
213
|
await import_fs_extra.default.writeFile(ymlPath, import_js_yaml.default.dump(config, { lineWidth: 120 }));
|
|
200
214
|
}
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
215
|
+
var SBOM_SIDECAR_CONTENT = `# stackwright.sbom.yml \u2014 supply-chain compliance config
|
|
216
|
+
# Consumed by @stackwright-pro/sbom-enterprise at build time via pnpm prebuild.
|
|
217
|
+
# See packages/sbom-enterprise/README.md for full option reference.
|
|
218
|
+
#
|
|
219
|
+
# API key is env-only \u2014 set SBOM_ENTERPRISE_OSV_API_KEY in .env.local (optional).
|
|
220
|
+
# Without the key, public OSV.dev rate limits apply (still functional).
|
|
221
|
+
features:
|
|
222
|
+
cveEnrichment: true # OSV.dev CVE enrichment \u2014 works without API key (rate-limited)
|
|
223
|
+
signing: false # Requires Sigstore OIDC token; opt-in via CI env
|
|
224
|
+
provenance: false # SLSA L3 requires CI build infra; opt-in
|
|
225
|
+
publishing: false # No registry writes by default
|
|
226
|
+
compliance:
|
|
227
|
+
frameworks:
|
|
228
|
+
- nist80053
|
|
229
|
+
- fedramp
|
|
230
|
+
outputFormats:
|
|
231
|
+
- oscal
|
|
232
|
+
- markdown
|
|
233
|
+
- csv
|
|
234
|
+
outputDir: .stackwright/compliance
|
|
235
|
+
impactLevel: Moderate
|
|
236
|
+
`;
|
|
237
|
+
async function emitSbomSidecar(targetDir) {
|
|
238
|
+
const sidecarPath = import_path.default.join(targetDir, "stackwright.sbom.yml");
|
|
239
|
+
if (await import_fs_extra.default.pathExists(sidecarPath)) {
|
|
240
|
+
return;
|
|
241
|
+
}
|
|
242
|
+
await import_fs_extra.default.writeFile(sidecarPath, SBOM_SIDECAR_CONTENT, "utf-8");
|
|
243
|
+
}
|
|
244
|
+
async function scaffoldWorkspaceConfig(targetDir) {
|
|
245
|
+
const workspaceDir = import_path.default.join(targetDir, ".code_puppy");
|
|
246
|
+
await import_fs_extra.default.ensureDir(workspaceDir);
|
|
247
|
+
const workspaceStats = await import_fs_extra.default.lstat(workspaceDir);
|
|
248
|
+
if (workspaceStats.isSymbolicLink()) {
|
|
249
|
+
throw new Error(".code_puppy/ is a symlink \u2014 refusing to write. Check for tampering.");
|
|
250
|
+
}
|
|
251
|
+
const agentsDir = import_path.default.join(workspaceDir, "agents");
|
|
252
|
+
await import_fs_extra.default.ensureDir(agentsDir);
|
|
253
|
+
if (await import_fs_extra.default.pathExists(agentsDir)) {
|
|
254
|
+
const agentsStats = await import_fs_extra.default.lstat(agentsDir);
|
|
255
|
+
if (agentsStats.isSymbolicLink()) {
|
|
256
|
+
throw new Error(".code_puppy/agents is a symlink \u2014 refusing to write. Check for tampering.");
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
const configPath = import_path.default.join(workspaceDir, "config.json");
|
|
260
|
+
let existingConfig = {};
|
|
261
|
+
if (import_fs_extra.default.existsSync(configPath)) {
|
|
262
|
+
try {
|
|
263
|
+
existingConfig = JSON.parse(await import_fs_extra.default.readFile(configPath, "utf-8"));
|
|
264
|
+
} catch {
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
if (await import_fs_extra.default.pathExists(configPath)) {
|
|
268
|
+
const configStats = await import_fs_extra.default.lstat(configPath);
|
|
269
|
+
if (configStats.isSymbolicLink()) {
|
|
270
|
+
throw new Error(
|
|
271
|
+
".code_puppy/config.json is a symlink \u2014 refusing to write. Check for tampering."
|
|
272
|
+
);
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
await import_fs_extra.default.writeFile(
|
|
276
|
+
configPath,
|
|
277
|
+
JSON.stringify({ ...existingConfig, projectOnly: true }, null, 2) + "\n",
|
|
278
|
+
"utf-8"
|
|
279
|
+
);
|
|
280
|
+
const mcpServersPath = import_path.default.join(workspaceDir, "mcp_servers.json");
|
|
281
|
+
if (await import_fs_extra.default.pathExists(mcpServersPath)) {
|
|
282
|
+
const mcpStats = await import_fs_extra.default.lstat(mcpServersPath);
|
|
283
|
+
if (mcpStats.isSymbolicLink()) {
|
|
284
|
+
throw new Error(
|
|
285
|
+
".code_puppy/mcp_servers.json is a symlink \u2014 refusing to write. Check for tampering."
|
|
286
|
+
);
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
if (!import_fs_extra.default.existsSync(mcpServersPath)) {
|
|
290
|
+
const mcpConfig = {
|
|
291
|
+
mcp_servers: {
|
|
292
|
+
"stackwright-pro-mcp": {
|
|
293
|
+
type: "stdio",
|
|
294
|
+
command: "pnpm",
|
|
295
|
+
args: ["exec", "stackwright-pro-mcp"],
|
|
296
|
+
enabled: true,
|
|
297
|
+
cwd: "${PROJECT_ROOT}"
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
};
|
|
301
|
+
await import_fs_extra.default.writeFile(mcpServersPath, JSON.stringify(mcpConfig, null, 2) + "\n", "utf-8");
|
|
205
302
|
}
|
|
206
|
-
const specFilename = import_path.default.basename(resolvedSpec);
|
|
207
|
-
const derivedName = specName || import_path.default.basename(specFilename, import_path.default.extname(specFilename));
|
|
208
|
-
const specsDir = import_path.default.join(targetDir, "specs");
|
|
209
|
-
await import_fs_extra.default.ensureDir(specsDir);
|
|
210
|
-
await import_fs_extra.default.copy(resolvedSpec, import_path.default.join(specsDir, specFilename));
|
|
211
|
-
return { specFilename, derivedName };
|
|
212
303
|
}
|
|
213
304
|
function generateReadme(options) {
|
|
214
305
|
const { projectName, hasSpec, specName, specFilename, hasOtters, hasMock } = options;
|
|
@@ -231,7 +322,7 @@ export const EquipmentSchema = z.object({
|
|
|
231
322
|
export type Equipment = z.infer<typeof EquipmentSchema>;
|
|
232
323
|
|
|
233
324
|
// src/generated/${specName}/client.ts
|
|
234
|
-
export class ${specName.charAt(0).toUpperCase() + specName.slice(1)}Client {
|
|
325
|
+
export class ${(specName ?? "api").charAt(0).toUpperCase() + (specName ?? "api").slice(1)}Client {
|
|
235
326
|
async getEquipment(id: string): Promise<Equipment> {
|
|
236
327
|
// Auto-wired with auth, validation, error handling
|
|
237
328
|
}
|
|
@@ -244,10 +335,10 @@ export class ${specName.charAt(0).toUpperCase() + specName.slice(1)}Client {
|
|
|
244
335
|
3. You import and use it:
|
|
245
336
|
|
|
246
337
|
\`\`\`typescript
|
|
247
|
-
import { ${specName.charAt(0).toUpperCase() + specName.slice(1)}Client } from '@/generated/${specName}/client';
|
|
338
|
+
import { ${(specName ?? "api").charAt(0).toUpperCase() + (specName ?? "api").slice(1)}Client } from '@/generated/${specName}/client';
|
|
248
339
|
import type { Equipment } from '@/generated/${specName}/types';
|
|
249
340
|
|
|
250
|
-
const client = new ${specName.charAt(0).toUpperCase() + specName.slice(1)}Client();
|
|
341
|
+
const client = new ${(specName ?? "api").charAt(0).toUpperCase() + (specName ?? "api").slice(1)}Client();
|
|
251
342
|
const gear: Equipment = await client.getEquipment('123');
|
|
252
343
|
\`\`\`
|
|
253
344
|
|
|
@@ -283,12 +374,10 @@ Your project includes **SIX specialized AI agents** in \`node_modules/@stackwrig
|
|
|
283
374
|
**Start the otter raft:**
|
|
284
375
|
|
|
285
376
|
\`\`\`bash
|
|
286
|
-
|
|
377
|
+
npx @stackwright-pro/raft
|
|
287
378
|
\`\`\`
|
|
288
379
|
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
The raft is a Python state machine with a Rich terminal UI \u2014 it asks questions about your brand, auth, and data preferences, then coordinates specialist otters to build your app. All user interaction happens in the terminal; code-puppy is called as a one-shot subprocess for creative tasks only.
|
|
380
|
+
The raft verifies otter integrity, writes project context, and spawns code-puppy in foreman mode. The foreman asks questions about your brand, auth, and data preferences, then coordinates specialist otters to build your app. All state lives in \`.stackwright/\` \u2014 interrupt and resume anytime.
|
|
292
381
|
|
|
293
382
|
**Example session prompt:**
|
|
294
383
|
|
|
@@ -331,12 +420,16 @@ ${mockSection}## \u{1F4C1} Project Structure
|
|
|
331
420
|
|
|
332
421
|
\`\`\`
|
|
333
422
|
.
|
|
423
|
+
\u251C\u2500\u2500 app/
|
|
424
|
+
\u2502 \u251C\u2500\u2500 layout.tsx # Root layout (StackwrightLayout + Providers)
|
|
425
|
+
\u2502 \u251C\u2500\u2500 page.tsx # Home page (renders root content)
|
|
426
|
+
\u2502 \u251C\u2500\u2500 [...slug]/
|
|
427
|
+
\u2502 \u2502 \u2514\u2500\u2500 page.tsx # Dynamic pages from YAML content
|
|
428
|
+
\u2502 \u2514\u2500\u2500 _components/
|
|
429
|
+
\u2502 \u251C\u2500\u2500 providers.tsx # Pro: AuthProvider + component registration
|
|
430
|
+
\u2502 \u2514\u2500\u2500 page-client.tsx # Client-side page renderer
|
|
334
431
|
\u251C\u2500\u2500 pages/
|
|
335
|
-
\u2502 \
|
|
336
|
-
\u2502 \u251C\u2500\u2500 index.tsx # Home page (auto-generated by Stackwright)
|
|
337
|
-
\u2502 \u251C\u2500\u2500 about/
|
|
338
|
-
\u2502 \u2502 \u2514\u2500\u2500 content.yml # Page content in YAML
|
|
339
|
-
\u2502 \u2514\u2500\u2500 ...
|
|
432
|
+
\u2502 \u2514\u2500\u2500 content.yml # Root page content in YAML
|
|
340
433
|
\u251C\u2500\u2500 lib/
|
|
341
434
|
\u2502 \u2514\u2500\u2500 mock-auth.ts # Dev-only auth mocking (no backend needed)
|
|
342
435
|
\u251C\u2500\u2500 scripts/
|
|
@@ -356,7 +449,7 @@ ${mockSection}## \u{1F4C1} Project Structure
|
|
|
356
449
|
\`\`\`
|
|
357
450
|
|
|
358
451
|
**Key Files:**
|
|
359
|
-
- \`
|
|
452
|
+
- \`app/_components/providers.tsx\` - Wraps your app with \`AuthProvider\` for RBAC
|
|
360
453
|
- \`stackwright.yml\` - Single source of truth for theme, auth roles, and API wiring
|
|
361
454
|
- \`lib/mock-auth.ts\` - Mocks CAC/PIV headers locally so you can test auth flows
|
|
362
455
|
- \`scripts/prebuild.js\` - Runs before dev/build to generate types from your OpenAPI spec
|
|
@@ -466,8 +559,28 @@ ${aiAgentsSection}## \u{1F4DA} Learn More
|
|
|
466
559
|
**Questions?** File an issue or ping us in the discussions. We're here to help you ship faster.
|
|
467
560
|
`;
|
|
468
561
|
}
|
|
562
|
+
|
|
563
|
+
// src/index.ts
|
|
564
|
+
(0, import_scaffold_hooks.registerProScaffoldHooks)();
|
|
565
|
+
var { version } = require_package();
|
|
566
|
+
async function copyProTemplate(templateName, destPath) {
|
|
567
|
+
const src = import_path2.default.resolve(__dirname, "..", "templates", "pro", templateName);
|
|
568
|
+
await import_fs_extra2.default.copy(src, destPath);
|
|
569
|
+
}
|
|
570
|
+
async function handleSpec(targetDir, specPath, specName) {
|
|
571
|
+
const resolvedSpec = import_path2.default.resolve(specPath);
|
|
572
|
+
if (!import_fs_extra2.default.existsSync(resolvedSpec)) {
|
|
573
|
+
throw new Error(`Spec file not found: ${resolvedSpec}`);
|
|
574
|
+
}
|
|
575
|
+
const specFilename = import_path2.default.basename(resolvedSpec);
|
|
576
|
+
const derivedName = specName || import_path2.default.basename(specFilename, import_path2.default.extname(specFilename));
|
|
577
|
+
const specsDir = import_path2.default.join(targetDir, "specs");
|
|
578
|
+
await import_fs_extra2.default.ensureDir(specsDir);
|
|
579
|
+
await import_fs_extra2.default.copy(resolvedSpec, import_path2.default.join(specsDir, specFilename));
|
|
580
|
+
return { specFilename, derivedName };
|
|
581
|
+
}
|
|
469
582
|
async function launch(targetDir, options) {
|
|
470
|
-
const dirName =
|
|
583
|
+
const dirName = import_path2.default.basename(targetDir);
|
|
471
584
|
console.log(import_chalk.default.cyan.bold("\n\u{1F6A2} Launching Stackwright Pro...\n"));
|
|
472
585
|
const scaffoldOpts = {
|
|
473
586
|
name: options.name || dirName,
|
|
@@ -483,11 +596,11 @@ async function launch(targetDir, options) {
|
|
|
483
596
|
await addProDepsToPackageJson(targetDir);
|
|
484
597
|
console.log(import_chalk.default.green("\u{1F9A6} Pro packages added to package.json"));
|
|
485
598
|
const verifyPkg = JSON.parse(
|
|
486
|
-
await
|
|
599
|
+
await import_fs_extra2.default.readFile(import_path2.default.join(targetDir, "package.json"), "utf-8")
|
|
487
600
|
);
|
|
488
|
-
const proDepCount = Object.keys(
|
|
489
|
-
|
|
490
|
-
).length;
|
|
601
|
+
const proDepCount = Object.keys(
|
|
602
|
+
typeof verifyPkg.dependencies === "object" && verifyPkg.dependencies !== null ? verifyPkg.dependencies : {}
|
|
603
|
+
).filter((k) => k.startsWith("@stackwright-pro")).length;
|
|
491
604
|
console.log(import_chalk.default.dim(` \u2192 package.json verified: ${proDepCount} @stackwright-pro/* deps ready`));
|
|
492
605
|
console.log(import_chalk.default.cyan("\n\u{1F4E6} Installing dependencies (this takes a moment)..."));
|
|
493
606
|
if (!options.skipInstall) {
|
|
@@ -522,33 +635,53 @@ async function launch(targetDir, options) {
|
|
|
522
635
|
const dependencyMode = "standalone";
|
|
523
636
|
await (0, import_scaffold_core.runScaffoldHooks)("postInstall", {
|
|
524
637
|
targetDir,
|
|
525
|
-
projectName: options.name ||
|
|
526
|
-
siteTitle: options.title || options.name ||
|
|
638
|
+
projectName: options.name || import_path2.default.basename(targetDir),
|
|
639
|
+
siteTitle: options.title || options.name || import_path2.default.basename(targetDir),
|
|
527
640
|
themeId: options.theme || "corporate",
|
|
528
641
|
packageJson: {},
|
|
529
642
|
dependencyMode
|
|
530
643
|
});
|
|
531
|
-
await copyProTemplate(
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
await copyProTemplate("
|
|
536
|
-
await copyProTemplate("
|
|
537
|
-
await
|
|
538
|
-
await copyProTemplate("
|
|
539
|
-
|
|
644
|
+
await copyProTemplate(
|
|
645
|
+
"pro-providers.tsx",
|
|
646
|
+
import_path2.default.join(targetDir, "app", "_components", "providers.tsx")
|
|
647
|
+
);
|
|
648
|
+
await copyProTemplate("content.yml", import_path2.default.join(targetDir, "pages", "content.yml"));
|
|
649
|
+
await copyProTemplate("next.config.js", import_path2.default.join(targetDir, "next.config.js"));
|
|
650
|
+
await import_fs_extra2.default.ensureDir(import_path2.default.join(targetDir, "lib"));
|
|
651
|
+
await copyProTemplate("mock-auth.ts", import_path2.default.join(targetDir, "lib", "mock-auth.ts"));
|
|
652
|
+
await copyProTemplate("yaml.d.ts", import_path2.default.join(targetDir, "yaml.d.ts"));
|
|
653
|
+
await import_fs_extra2.default.ensureDir(import_path2.default.join(targetDir, "scripts"));
|
|
654
|
+
await copyProTemplate("prebuild.js", import_path2.default.join(targetDir, "scripts", "prebuild.js"));
|
|
655
|
+
await copyProTemplate(
|
|
656
|
+
"scripts/pro-content-plugin.js",
|
|
657
|
+
import_path2.default.join(targetDir, "scripts", "pro-content-plugin.js")
|
|
658
|
+
);
|
|
659
|
+
await copyProTemplate(
|
|
660
|
+
"scripts/start-mocks.js",
|
|
661
|
+
import_path2.default.join(targetDir, "scripts", "start-mocks.js")
|
|
662
|
+
);
|
|
663
|
+
await import_fs_extra2.default.ensureDir(import_path2.default.join(targetDir, "app", "[...slug]"));
|
|
664
|
+
await copyProTemplate(
|
|
665
|
+
"app/slug-page/page.tsx",
|
|
666
|
+
import_path2.default.join(targetDir, "app", "[...slug]", "page.tsx")
|
|
667
|
+
);
|
|
668
|
+
console.log(import_chalk.default.green(" Auth integration added (App Router providers + RBAC)"));
|
|
540
669
|
await addAuthToStackwrightYml(targetDir);
|
|
670
|
+
await emitSbomSidecar(targetDir);
|
|
671
|
+
console.log(import_chalk.default.green("\u{1F512} stackwright.sbom.yml emitted (NIST 800-53 + FedRAMP defaults)"));
|
|
541
672
|
let specInfo = null;
|
|
542
673
|
const MOCK_URL = "http://localhost:4010";
|
|
543
|
-
if (options.spec) {
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
674
|
+
if (options.spec && options.spec.length > 0) {
|
|
675
|
+
for (const specPath of options.spec) {
|
|
676
|
+
specInfo = await handleSpec(targetDir, specPath, options.specName);
|
|
677
|
+
await addIntegrationToStackwrightYml(
|
|
678
|
+
targetDir,
|
|
679
|
+
specInfo.derivedName,
|
|
680
|
+
specInfo.specFilename,
|
|
681
|
+
options.mock ? MOCK_URL : void 0
|
|
682
|
+
);
|
|
683
|
+
}
|
|
684
|
+
console.log(import_chalk.default.green(`\u{1F4E1} ${options.spec.length} OpenAPI integration(s) wired up`));
|
|
552
685
|
if (options.mock) {
|
|
553
686
|
console.log(import_chalk.default.green("\u{1F3AD} Prism mock server configured"));
|
|
554
687
|
}
|
|
@@ -558,9 +691,9 @@ async function launch(targetDir, options) {
|
|
|
558
691
|
"\u26A0\uFE0F No spec provided with --mock. Using sample Petstore spec for demo. Replace with your API spec."
|
|
559
692
|
)
|
|
560
693
|
);
|
|
561
|
-
const specsDir =
|
|
562
|
-
await
|
|
563
|
-
await copyProTemplate("petstore.yaml",
|
|
694
|
+
const specsDir = import_path2.default.join(targetDir, "specs");
|
|
695
|
+
await import_fs_extra2.default.ensureDir(specsDir);
|
|
696
|
+
await copyProTemplate("petstore.yaml", import_path2.default.join(specsDir, "petstore.yaml"));
|
|
564
697
|
specInfo = { specFilename: "petstore.yaml", derivedName: "petstore" };
|
|
565
698
|
await addIntegrationToStackwrightYml(
|
|
566
699
|
targetDir,
|
|
@@ -579,13 +712,33 @@ async function launch(targetDir, options) {
|
|
|
579
712
|
hasOtters: !options.skipOtters,
|
|
580
713
|
hasMock: !!options.mock
|
|
581
714
|
});
|
|
582
|
-
await
|
|
715
|
+
await import_fs_extra2.default.writeFile(import_path2.default.join(targetDir, "README.md"), readmeContent);
|
|
583
716
|
console.log(import_chalk.default.green("\u{1F4C4} README.md created"));
|
|
584
|
-
const
|
|
717
|
+
const initContext = {
|
|
718
|
+
projectRoot: targetDir,
|
|
719
|
+
projectName: options.name || dirName,
|
|
720
|
+
generatedBy: "launch-stackwright-pro",
|
|
721
|
+
version: "1.0"
|
|
722
|
+
};
|
|
723
|
+
if (specInfo) {
|
|
724
|
+
initContext.specPath = `specs/${specInfo.specFilename}`;
|
|
725
|
+
initContext.specName = specInfo.derivedName;
|
|
726
|
+
}
|
|
727
|
+
initContext.theme = options.theme || "corporate";
|
|
728
|
+
const stackwrightDir = import_path2.default.join(targetDir, ".stackwright");
|
|
729
|
+
await import_fs_extra2.default.ensureDir(stackwrightDir);
|
|
730
|
+
await import_fs_extra2.default.writeFile(
|
|
731
|
+
import_path2.default.join(stackwrightDir, "init-context.json"),
|
|
732
|
+
JSON.stringify(initContext, null, 2) + "\n",
|
|
733
|
+
"utf-8"
|
|
734
|
+
);
|
|
735
|
+
await scaffoldWorkspaceConfig(targetDir);
|
|
736
|
+
console.log(import_chalk.default.green("\u{1F43E} .code_puppy/ workspace scaffolded (projectOnly: true)"));
|
|
737
|
+
const relDir = import_path2.default.relative(process.cwd(), targetDir) || ".";
|
|
585
738
|
console.log(import_chalk.default.cyan.bold("\n\u{1F389} All set! Here's what to do next:\n"));
|
|
586
739
|
console.log(import_chalk.default.white(` 1. cd ${relDir}`));
|
|
587
|
-
console.log(import_chalk.default.white(" 2.
|
|
588
|
-
console.log(import_chalk.default.dim(" (
|
|
740
|
+
console.log(import_chalk.default.white(" 2. npx @stackwright-pro/raft"));
|
|
741
|
+
console.log(import_chalk.default.dim(" (use --verbose for troubleshooting)"));
|
|
589
742
|
if (specInfo) {
|
|
590
743
|
console.log(import_chalk.default.cyan(`
|
|
591
744
|
\u{1F4E1} Your API spec was copied to specs/${specInfo.specFilename}`));
|
|
@@ -603,8 +756,8 @@ async function launch(targetDir, options) {
|
|
|
603
756
|
async function main() {
|
|
604
757
|
const program = new import_commander.Command();
|
|
605
758
|
program.name("launch-stackwright-pro").description("\u{1F6A2} Launch a new Stackwright Pro project with auth, OpenAPI, and the otter raft").version(version).argument("[directory]", "Project directory", ".").option("--name <name>", "Project name (used in package.json)").option("--title <title>", "Site title shown in the app bar and browser tab").option("--theme <themeId>", "Theme ID (e.g., corporate, creative, minimal)").option("--force", "Launch even if the target directory is not empty").option("--skip-otters", "Skip copying otter raft configs").option("-y, --yes", "Skip all prompts, use defaults").option("--mock", "Configure Prism mock server for API development (runs on port 4010)").option(
|
|
606
|
-
"--spec <
|
|
607
|
-
"
|
|
759
|
+
"--spec <paths...>",
|
|
760
|
+
"Paths to OpenAPI specs (YAML or JSON) \u2014 copies into project and wires up integrations (can be specified multiple times)"
|
|
608
761
|
).option(
|
|
609
762
|
"--spec-name <name>",
|
|
610
763
|
"Name for the API integration (default: derived from spec filename)"
|
|
@@ -613,8 +766,8 @@ async function main() {
|
|
|
613
766
|
"Skip automatic pnpm install (useful in CI or when using a different package manager)"
|
|
614
767
|
).action(async (directory, options) => {
|
|
615
768
|
const homeDir = import_os.default.homedir();
|
|
616
|
-
const targetDir =
|
|
617
|
-
if (!targetDir.startsWith(homeDir +
|
|
769
|
+
const targetDir = directory !== "." ? import_path2.default.resolve(process.cwd(), directory) : options.name ? import_path2.default.resolve(process.cwd(), options.name) : process.cwd();
|
|
770
|
+
if (!targetDir.startsWith(homeDir + import_path2.default.sep) && targetDir !== homeDir) {
|
|
618
771
|
console.warn(import_chalk.default.yellow(`\u26A0\uFE0F Scaffolding outside your home directory: ${targetDir}`));
|
|
619
772
|
}
|
|
620
773
|
await launch(targetDir, options);
|