create-du-app 0.1.2 → 0.1.3
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 +54 -37
- package/package.json +1 -1
- package/src/generate.js +246 -15
- package/src/registry.js +3 -1
- package/templates/shared/README.md +21 -6
- package/templates/shared/_package.json +26 -4
- package/templates/shared/src/index.ts +12 -0
- package/templates/shared/tsconfig.json +8 -0
- package/templates/shared/tsup.config.ts +9 -0
- package/templates/shared/index.js +0 -4
package/README.md
CHANGED
|
@@ -1,64 +1,81 @@
|
|
|
1
1
|
# create-du-app
|
|
2
2
|
|
|
3
|
-
>
|
|
3
|
+
> Scaffold a standalone product monorepo from company templates — in one command.
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
pnpm workspace.
|
|
5
|
+
Run it, answer a couple of prompts (project name + which stacks), and you get a ready-to-run
|
|
6
|
+
pnpm monorepo with only the stacks you picked.
|
|
8
7
|
|
|
9
|
-
##
|
|
8
|
+
## Quick start
|
|
9
|
+
|
|
10
|
+
No install needed. In a real terminal (Terminal.app / iTerm):
|
|
10
11
|
|
|
11
12
|
```bash
|
|
12
|
-
|
|
13
|
-
pnpm install
|
|
14
|
-
pnpm link --global # registers the global `create-app` command
|
|
13
|
+
npx create-du-app
|
|
15
14
|
```
|
|
16
15
|
|
|
17
|
-
|
|
16
|
+
You'll be asked:
|
|
17
|
+
|
|
18
|
+
1. **Project name?** → e.g. `my-shop`
|
|
19
|
+
2. **Select the groups** → press **`Space`** to tick Mobile / Frontend / Backend, then **`Enter`**
|
|
20
|
+
3. **Pick one technology** per group → **↑ / ↓** then **`Enter`**
|
|
21
|
+
|
|
22
|
+
Then:
|
|
18
23
|
|
|
19
24
|
```bash
|
|
20
|
-
|
|
25
|
+
cd my-shop && pnpm install
|
|
21
26
|
```
|
|
22
27
|
|
|
23
|
-
|
|
28
|
+
Done. 🎉
|
|
24
29
|
|
|
25
|
-
|
|
30
|
+
## Non-interactive (CI / IDE terminals)
|
|
31
|
+
|
|
32
|
+
Pass everything as flags — no prompts, works anywhere:
|
|
26
33
|
|
|
27
34
|
```bash
|
|
28
|
-
create-app
|
|
35
|
+
npx create-du-app --name my-shop --mobile expo --fe nextjs --be nestjs
|
|
29
36
|
```
|
|
30
37
|
|
|
31
|
-
|
|
38
|
+
| Flag | Values | Required |
|
|
39
|
+
|------|--------|----------|
|
|
40
|
+
| `--name` | your project name | ✅ |
|
|
41
|
+
| `--mobile` | `rn` · `expo` · `flutter` | optional |
|
|
42
|
+
| `--fe` | `nextjs` · `reactjs` | optional |
|
|
43
|
+
| `--be` | `nodejs` · `nestjs` · `php` | optional |
|
|
44
|
+
|
|
45
|
+
## Install globally (optional)
|
|
46
|
+
|
|
47
|
+
Scaffold often? Install once and use the shorter `create-app` command:
|
|
32
48
|
|
|
33
49
|
```bash
|
|
34
|
-
|
|
50
|
+
npm install -g create-du-app # or: pnpm add -g create-du-app
|
|
51
|
+
create-app # same as npx create-du-app
|
|
35
52
|
```
|
|
36
53
|
|
|
37
|
-
|
|
54
|
+
> The **package** is `create-du-app`; the installed **command** is `create-app`
|
|
55
|
+
> (same pattern as `create-next-app`).
|
|
38
56
|
|
|
39
|
-
|
|
40
|
-
|----------------------|------------------------------------------|
|
|
41
|
-
| `-n`, `--name <name>`| Project name |
|
|
42
|
-
| `--mobile <id>` | `rn` · `expo` · `flutter` |
|
|
43
|
-
| `--fe <id>` | `nextjs` · `reactjs` |
|
|
44
|
-
| `--be <id>` | `nodejs` · `nestjs` · `php` |
|
|
45
|
-
| `-h`, `--help` | Print help |
|
|
46
|
-
| `-v`, `--version` | Print version |
|
|
57
|
+
## Available stacks
|
|
47
58
|
|
|
48
|
-
|
|
59
|
+
| Group | Technologies | Output folder |
|
|
60
|
+
|----------|-------------------------------|----------------|
|
|
61
|
+
| Mobile | React Native · Expo · Flutter | `apps/mobile` |
|
|
62
|
+
| Frontend | Next.js · ReactJS | `apps/web` |
|
|
63
|
+
| Backend | Node.js · NestJS · PHP | `apps/backend` |
|
|
49
64
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
| `registry.js` | **Data declaration** of groups, technologies, `templatePath`, and `lang`. The only file to edit when adding or removing a stack. |
|
|
54
|
-
| `prompts.js` | Interactive prompts (`@clack/prompts`): project name → multi-select groups → select one technology per group. |
|
|
55
|
-
| `generate.js` | Copies templates, renames `_package.json` → `package.json`, generates the root `package.json` + `pnpm-workspace.yaml`, conditionally creates `packages/shared`, and substitutes `{{PROJECT_NAME}}`. |
|
|
65
|
+
`packages/shared` (shared types + api client) is added automatically when you pick **≥ 2** JS/TS stacks.
|
|
66
|
+
|
|
67
|
+
---
|
|
56
68
|
|
|
57
|
-
##
|
|
69
|
+
## For maintainers
|
|
58
70
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
71
|
+
Working on the CLI source or the templates? See the
|
|
72
|
+
[repository README](../../README.md) and **[CONTRIBUTING.md](../../CONTRIBUTING.md)**.
|
|
73
|
+
|
|
74
|
+
| File | Responsibility |
|
|
75
|
+
|---------------|----------------|
|
|
76
|
+
| `src/index.js` | Entry point. Parses flags, runs interactive or non-interactive, then generates. |
|
|
77
|
+
| `src/registry.js` | **Data declaration** of groups, technologies, `templatePath`, and `lang`. Edit this to add/remove a stack. |
|
|
78
|
+
| `src/prompts.js` | Interactive prompts (`@clack/prompts`). |
|
|
79
|
+
| `src/generate.js` | Copies templates, renames `_package.json` → `package.json`, emits the root `package.json` + `pnpm-workspace.yaml`, substitutes `{{PROJECT_NAME}}`. |
|
|
63
80
|
|
|
64
|
-
|
|
81
|
+
Release a new version: `pnpm release` (see [scripts/release.mjs](scripts/release.mjs)).
|
package/package.json
CHANGED
package/src/generate.js
CHANGED
|
@@ -1,13 +1,24 @@
|
|
|
1
|
-
// generate.js — copy templates + rename + generate dynamic
|
|
1
|
+
// generate.js — copy templates + rename + generate dynamic root config + replace placeholders.
|
|
2
2
|
//
|
|
3
3
|
// Input: the plan from prompts.js → { projectName, selections: [{group, option}] }
|
|
4
4
|
// and `repoRoot` (the company-starter root that contains templates/).
|
|
5
|
+
//
|
|
6
|
+
// The output is a standard pnpm + Turborepo monorepo (modeled on the life-master layout):
|
|
7
|
+
// <project>/
|
|
8
|
+
// package.json (turbo scripts + devDeps)
|
|
9
|
+
// pnpm-workspace.yaml
|
|
10
|
+
// turbo.json
|
|
11
|
+
// tsconfig.base.json (paths: @repo/* -> packages/*/src)
|
|
12
|
+
// .gitignore .npmrc
|
|
13
|
+
// apps/<web|backend|mobile>/ (selected stacks — source dropped in later)
|
|
14
|
+
// packages/shared/ (@repo/shared — only when >= 2 JS/TS apps)
|
|
5
15
|
|
|
6
16
|
import path from 'node:path';
|
|
7
17
|
import fse from 'fs-extra';
|
|
8
18
|
import {
|
|
9
19
|
SHARED_TEMPLATE_PATH,
|
|
10
20
|
SHARED_OUTPUT_DIR,
|
|
21
|
+
SHARED_PACKAGE_NAME,
|
|
11
22
|
} from './registry.js';
|
|
12
23
|
|
|
13
24
|
const PLACEHOLDER = /\{\{PROJECT_NAME\}\}/g;
|
|
@@ -38,7 +49,6 @@ function renameBasename(name) {
|
|
|
38
49
|
|
|
39
50
|
// Copy one template folder → destination, renaming files and replacing {{PROJECT_NAME}}.
|
|
40
51
|
async function copyTemplate(srcDir, destDir, projectName) {
|
|
41
|
-
// An empty template (real source not dropped in yet) must still work.
|
|
42
52
|
if (!(await fse.pathExists(srcDir))) {
|
|
43
53
|
throw new Error(`Template not found: ${srcDir}`);
|
|
44
54
|
}
|
|
@@ -66,14 +76,33 @@ async function copyTemplate(srcDir, destDir, projectName) {
|
|
|
66
76
|
}
|
|
67
77
|
}
|
|
68
78
|
|
|
69
|
-
//
|
|
70
|
-
|
|
79
|
+
// ---------------------------------------------------------------------------
|
|
80
|
+
// Dynamic root files (pnpm + Turborepo standard).
|
|
81
|
+
// ---------------------------------------------------------------------------
|
|
82
|
+
|
|
83
|
+
// Build the project's root package.json (pnpm: NO "workspaces" field).
|
|
84
|
+
function buildRootPackageJson(projectName, hasShared) {
|
|
71
85
|
return {
|
|
72
86
|
name: projectName,
|
|
73
87
|
version: '0.1.0',
|
|
74
88
|
private: true,
|
|
75
|
-
|
|
76
|
-
|
|
89
|
+
packageManager: 'pnpm@9.12.0',
|
|
90
|
+
engines: { node: '>=20' },
|
|
91
|
+
scripts: {
|
|
92
|
+
bootstrap: 'node scripts/bootstrap.mjs',
|
|
93
|
+
build: 'turbo run build',
|
|
94
|
+
dev: 'turbo run dev',
|
|
95
|
+
lint: 'turbo run lint',
|
|
96
|
+
typecheck: 'turbo run typecheck',
|
|
97
|
+
test: 'turbo run test',
|
|
98
|
+
...(hasShared
|
|
99
|
+
? { 'shared:build': `turbo run build --filter=${SHARED_PACKAGE_NAME}` }
|
|
100
|
+
: {}),
|
|
101
|
+
},
|
|
102
|
+
devDependencies: {
|
|
103
|
+
turbo: '^2.3.3',
|
|
104
|
+
typescript: '^5.8.3',
|
|
105
|
+
},
|
|
77
106
|
};
|
|
78
107
|
}
|
|
79
108
|
|
|
@@ -85,6 +114,181 @@ function buildPnpmWorkspaceYaml(createdAppDirs) {
|
|
|
85
114
|
return `packages:\n${lines.join('\n')}\n`;
|
|
86
115
|
}
|
|
87
116
|
|
|
117
|
+
const TURBO_JSON = {
|
|
118
|
+
$schema: 'https://turbo.build/schema.json',
|
|
119
|
+
tasks: {
|
|
120
|
+
build: {
|
|
121
|
+
dependsOn: ['^build'],
|
|
122
|
+
outputs: ['dist/**', '.next/**', '!.next/cache/**', 'build/**'],
|
|
123
|
+
},
|
|
124
|
+
lint: { dependsOn: ['^build'] },
|
|
125
|
+
typecheck: { dependsOn: ['^build'] },
|
|
126
|
+
test: { dependsOn: ['^build'], outputs: ['coverage/**'] },
|
|
127
|
+
dev: { cache: false, persistent: true },
|
|
128
|
+
},
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
const TSCONFIG_BASE = {
|
|
132
|
+
$schema: 'https://json.schemastore.org/tsconfig',
|
|
133
|
+
compilerOptions: {
|
|
134
|
+
target: 'ESNext',
|
|
135
|
+
module: 'ESNext',
|
|
136
|
+
moduleResolution: 'Bundler',
|
|
137
|
+
lib: ['ESNext'],
|
|
138
|
+
strict: true,
|
|
139
|
+
esModuleInterop: true,
|
|
140
|
+
allowSyntheticDefaultImports: true,
|
|
141
|
+
forceConsistentCasingInFileNames: true,
|
|
142
|
+
skipLibCheck: true,
|
|
143
|
+
resolveJsonModule: true,
|
|
144
|
+
isolatedModules: true,
|
|
145
|
+
declaration: true,
|
|
146
|
+
baseUrl: '.',
|
|
147
|
+
paths: { '@repo/*': ['packages/*/src'] },
|
|
148
|
+
},
|
|
149
|
+
exclude: ['node_modules', 'dist', 'build'],
|
|
150
|
+
};
|
|
151
|
+
|
|
152
|
+
const GITIGNORE = `# Dependencies
|
|
153
|
+
node_modules/
|
|
154
|
+
.pnpm-store/
|
|
155
|
+
|
|
156
|
+
# Turborepo
|
|
157
|
+
.turbo/
|
|
158
|
+
|
|
159
|
+
# Build outputs
|
|
160
|
+
dist/
|
|
161
|
+
build/
|
|
162
|
+
out/
|
|
163
|
+
.next/
|
|
164
|
+
*.tsbuildinfo
|
|
165
|
+
|
|
166
|
+
# Expo / React Native
|
|
167
|
+
.expo/
|
|
168
|
+
web-build/
|
|
169
|
+
.metro-health-check*
|
|
170
|
+
|
|
171
|
+
# Environment variables
|
|
172
|
+
.env
|
|
173
|
+
.env.*
|
|
174
|
+
!.env.example
|
|
175
|
+
!.env.*.example
|
|
176
|
+
|
|
177
|
+
# Testing
|
|
178
|
+
coverage/
|
|
179
|
+
|
|
180
|
+
# Logs
|
|
181
|
+
*.log
|
|
182
|
+
npm-debug.log*
|
|
183
|
+
pnpm-debug.log*
|
|
184
|
+
yarn-error.log*
|
|
185
|
+
|
|
186
|
+
# OS
|
|
187
|
+
.DS_Store
|
|
188
|
+
|
|
189
|
+
# IDE
|
|
190
|
+
.idea/
|
|
191
|
+
.vscode/
|
|
192
|
+
*.iml
|
|
193
|
+
`;
|
|
194
|
+
|
|
195
|
+
const NPMRC = `# Hoist node_modules so Metro (Expo / React Native) resolves packages and
|
|
196
|
+
# workspace dependencies (@repo/*) correctly. Harmless for web/back-end apps.
|
|
197
|
+
node-linker=hoisted
|
|
198
|
+
|
|
199
|
+
# Keep peer-dependency resolution lenient — the RN ecosystem has many loose
|
|
200
|
+
# peer ranges that would otherwise fail install.
|
|
201
|
+
strict-peer-dependencies=false
|
|
202
|
+
`;
|
|
203
|
+
|
|
204
|
+
// One-command bootstrap, written to the generated repo as scripts/bootstrap.mjs.
|
|
205
|
+
// Run it (`pnpm bootstrap`) AFTER dropping the real stack source in: it installs
|
|
206
|
+
// deps, builds @repo/shared, then verifies the apps can resolve it.
|
|
207
|
+
// NOTE: not named "setup" because `pnpm setup` is a reserved pnpm built-in.
|
|
208
|
+
const SETUP_MJS = `// bootstrap.mjs — one-command bootstrap for this monorepo.
|
|
209
|
+
//
|
|
210
|
+
// Run it after you have dropped the real source into apps/* :
|
|
211
|
+
//
|
|
212
|
+
// pnpm bootstrap
|
|
213
|
+
//
|
|
214
|
+
// It installs dependencies, builds @repo/shared (so its dist/ exists), and
|
|
215
|
+
// verifies every app that depends on it can resolve \`@repo/shared\`.
|
|
216
|
+
|
|
217
|
+
import { spawnSync } from 'node:child_process';
|
|
218
|
+
import { existsSync, readFileSync, readdirSync } from 'node:fs';
|
|
219
|
+
import { createRequire } from 'node:module';
|
|
220
|
+
import path from 'node:path';
|
|
221
|
+
import { fileURLToPath } from 'node:url';
|
|
222
|
+
|
|
223
|
+
const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
|
|
224
|
+
const SHARED = '@repo/shared';
|
|
225
|
+
|
|
226
|
+
function sh(command) {
|
|
227
|
+
console.log(\`\\n$ \${command}\`);
|
|
228
|
+
const r = spawnSync(command, { cwd: root, stdio: 'inherit', shell: true });
|
|
229
|
+
if (r.status !== 0) {
|
|
230
|
+
console.error(\`\\n\\u2717 Failed: \${command}\`);
|
|
231
|
+
process.exit(r.status ?? 1);
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// 1) Install all workspace dependencies.
|
|
236
|
+
sh('pnpm install');
|
|
237
|
+
|
|
238
|
+
// 2) Build the shared package (if present) so apps can import its dist output.
|
|
239
|
+
const sharedDir = path.join(root, 'packages', 'shared');
|
|
240
|
+
const hasShared = existsSync(sharedDir);
|
|
241
|
+
if (hasShared) {
|
|
242
|
+
sh(\`pnpm --filter \${SHARED} build\`);
|
|
243
|
+
} else {
|
|
244
|
+
console.log('\\n\\u2022 No packages/shared in this repo — skipping shared build.');
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
// 3) Verify each app that depends on @repo/shared can actually resolve it.
|
|
248
|
+
if (hasShared) {
|
|
249
|
+
const appsDir = path.join(root, 'apps');
|
|
250
|
+
const apps = existsSync(appsDir)
|
|
251
|
+
? readdirSync(appsDir, { withFileTypes: true }).filter((e) => e.isDirectory())
|
|
252
|
+
: [];
|
|
253
|
+
|
|
254
|
+
let failures = 0;
|
|
255
|
+
for (const app of apps) {
|
|
256
|
+
const pkgPath = path.join(appsDir, app.name, 'package.json');
|
|
257
|
+
if (!existsSync(pkgPath)) continue;
|
|
258
|
+
const pkg = JSON.parse(readFileSync(pkgPath, 'utf8'));
|
|
259
|
+
const deps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
260
|
+
if (!deps[SHARED]) continue;
|
|
261
|
+
|
|
262
|
+
const require = createRequire(path.join(appsDir, app.name, 'noop.js'));
|
|
263
|
+
try {
|
|
264
|
+
require.resolve(SHARED);
|
|
265
|
+
console.log(\`\\n\\u2713 apps/\${app.name} resolves \${SHARED}\`);
|
|
266
|
+
} catch {
|
|
267
|
+
console.error(\`\\n\\u2717 apps/\${app.name} cannot resolve \${SHARED}\`);
|
|
268
|
+
failures++;
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
if (failures > 0) {
|
|
273
|
+
console.error(\`\\n\${failures} app(s) cannot resolve \${SHARED}. Check the \"\${SHARED}\" dependency and that its build produced dist/.\`);
|
|
274
|
+
process.exit(1);
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
console.log('\\n\\u2713 Setup complete. Start developing with: pnpm dev');
|
|
279
|
+
`;
|
|
280
|
+
|
|
281
|
+
// ---------------------------------------------------------------------------
|
|
282
|
+
|
|
283
|
+
// Add "@repo/shared": "workspace:*" to a JS/TS app's package.json (if present).
|
|
284
|
+
async function wireSharedDependency(appPkgPath) {
|
|
285
|
+
if (!(await fse.pathExists(appPkgPath))) return false;
|
|
286
|
+
const pkg = await fse.readJson(appPkgPath);
|
|
287
|
+
pkg.dependencies = { ...(pkg.dependencies ?? {}), [SHARED_PACKAGE_NAME]: 'workspace:*' };
|
|
288
|
+
await fse.writeJson(appPkgPath, pkg, { spaces: 2 });
|
|
289
|
+
return true;
|
|
290
|
+
}
|
|
291
|
+
|
|
88
292
|
export async function generate(plan, repoRoot, cwd = process.cwd()) {
|
|
89
293
|
const { projectName, selections } = plan;
|
|
90
294
|
const projectRoot = path.resolve(cwd, projectName);
|
|
@@ -107,27 +311,54 @@ export async function generate(plan, repoRoot, cwd = process.cwd()) {
|
|
|
107
311
|
}
|
|
108
312
|
|
|
109
313
|
// 2) packages/shared is CONDITIONAL: >= 2 selections with lang === 'js'.
|
|
110
|
-
const
|
|
111
|
-
|
|
314
|
+
const jsSelections = selections.filter((s) => s.option.lang === 'js');
|
|
315
|
+
const hasShared = jsSelections.length >= 2;
|
|
316
|
+
if (hasShared) {
|
|
112
317
|
const srcDir = path.resolve(repoRoot, SHARED_TEMPLATE_PATH);
|
|
113
318
|
const destDir = path.resolve(projectRoot, SHARED_OUTPUT_DIR);
|
|
114
319
|
await copyTemplate(srcDir, destDir, projectName);
|
|
115
|
-
logs.push(`✓ shared → ${SHARED_OUTPUT_DIR} (${
|
|
320
|
+
logs.push(`✓ shared → ${SHARED_OUTPUT_DIR} (${jsSelections.length} JS/TS apps)`);
|
|
321
|
+
|
|
322
|
+
// Wire each JS/TS app to depend on the shared package.
|
|
323
|
+
for (const { group } of jsSelections) {
|
|
324
|
+
const appPkg = path.join(projectRoot, group.outputDir, 'package.json');
|
|
325
|
+
if (await wireSharedDependency(appPkg)) {
|
|
326
|
+
logs.push(` ↳ ${group.outputDir} depends on ${SHARED_PACKAGE_NAME} (workspace:*)`);
|
|
327
|
+
}
|
|
328
|
+
}
|
|
116
329
|
} else {
|
|
117
|
-
logs.push(`• Skipping shared: not enough JS/TS apps (need >= 2, have ${
|
|
330
|
+
logs.push(`• Skipping shared: not enough JS/TS apps (need >= 2, have ${jsSelections.length}).`);
|
|
118
331
|
}
|
|
119
332
|
|
|
120
|
-
// 3) Generate the root
|
|
121
|
-
|
|
122
|
-
|
|
333
|
+
// 3) Generate the root config files dynamically.
|
|
334
|
+
await fse.writeJson(
|
|
335
|
+
path.join(projectRoot, 'package.json'),
|
|
336
|
+
buildRootPackageJson(projectName, hasShared),
|
|
337
|
+
{ spaces: 2 },
|
|
338
|
+
);
|
|
123
339
|
logs.push('✓ package.json');
|
|
124
340
|
|
|
125
|
-
const wsEntries = [...createdAppDirs, 'packages/*'];
|
|
126
341
|
await fse.writeFile(
|
|
127
342
|
path.join(projectRoot, 'pnpm-workspace.yaml'),
|
|
128
343
|
buildPnpmWorkspaceYaml(createdAppDirs),
|
|
129
344
|
);
|
|
130
|
-
logs.push(`✓ pnpm-workspace.yaml (packages: ${
|
|
345
|
+
logs.push(`✓ pnpm-workspace.yaml (packages: ${[...createdAppDirs, 'packages/*'].join(', ')})`);
|
|
346
|
+
|
|
347
|
+
await fse.writeJson(path.join(projectRoot, 'turbo.json'), TURBO_JSON, { spaces: 2 });
|
|
348
|
+
logs.push('✓ turbo.json');
|
|
349
|
+
|
|
350
|
+
await fse.writeJson(path.join(projectRoot, 'tsconfig.base.json'), TSCONFIG_BASE, { spaces: 2 });
|
|
351
|
+
logs.push('✓ tsconfig.base.json');
|
|
352
|
+
|
|
353
|
+
await fse.writeFile(path.join(projectRoot, '.gitignore'), GITIGNORE);
|
|
354
|
+
logs.push('✓ .gitignore');
|
|
355
|
+
|
|
356
|
+
await fse.writeFile(path.join(projectRoot, '.npmrc'), NPMRC);
|
|
357
|
+
logs.push('✓ .npmrc');
|
|
358
|
+
|
|
359
|
+
await fse.ensureDir(path.join(projectRoot, 'scripts'));
|
|
360
|
+
await fse.writeFile(path.join(projectRoot, 'scripts', 'bootstrap.mjs'), SETUP_MJS);
|
|
361
|
+
logs.push('✓ scripts/bootstrap.mjs (run `pnpm bootstrap` after dropping source in)');
|
|
131
362
|
|
|
132
363
|
return { projectRoot, logs };
|
|
133
364
|
}
|
package/src/registry.js
CHANGED
|
@@ -43,9 +43,11 @@ export const GROUPS = [
|
|
|
43
43
|
},
|
|
44
44
|
];
|
|
45
45
|
|
|
46
|
-
//
|
|
46
|
+
// Shared package config. Only copied when >= 2 selections have lang === 'js'.
|
|
47
|
+
// Uses the `@repo/*` scope so it matches the `paths` mapping in tsconfig.base.json.
|
|
47
48
|
export const SHARED_TEMPLATE_PATH = 'templates/shared';
|
|
48
49
|
export const SHARED_OUTPUT_DIR = 'packages/shared';
|
|
50
|
+
export const SHARED_PACKAGE_NAME = '@repo/shared';
|
|
49
51
|
|
|
50
52
|
// Quickly look up a single option by (groupId, optionId).
|
|
51
53
|
export function findOption(groupId, optionId) {
|
|
@@ -1,11 +1,26 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @repo/shared (packages/shared)
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Shared data contracts (types, enums, API URLs, schemas) consumed by every JS/TS
|
|
4
|
+
app in the monorepo via `import { ... } from '@repo/shared'`.
|
|
4
5
|
|
|
5
|
-
|
|
6
|
+
## Layout
|
|
7
|
+
|
|
8
|
+
- `src/index.ts` — barrel file. Re-export everything you want apps to consume.
|
|
9
|
+
- `tsup.config.ts` — builds `dist/` (ESM + CJS + `.d.ts`).
|
|
10
|
+
- `tsconfig.json` — extends the root `tsconfig.base.json`.
|
|
11
|
+
|
|
12
|
+
## How it is wired
|
|
13
|
+
|
|
14
|
+
- Built with **tsup** (`pnpm --filter @repo/shared build`); output goes to `dist/`.
|
|
15
|
+
- Each JS/TS app gets `"@repo/shared": "workspace:*"` added automatically when
|
|
16
|
+
this package is generated.
|
|
17
|
+
- Turborepo runs `^build` first, so `@repo/shared` is built before any app that
|
|
18
|
+
depends on it.
|
|
19
|
+
|
|
20
|
+
## Conventions for template authors
|
|
6
21
|
|
|
7
22
|
- Name the template's `package.json` as **`_package.json`** (the CLI renames it on generate).
|
|
8
|
-
- Wherever you need the project name, write `{{PROJECT_NAME}}` — the CLI replaces it
|
|
23
|
+
- Wherever you need the project name, write `{{PROJECT_NAME}}` — the CLI replaces it.
|
|
9
24
|
|
|
10
|
-
> This package is **created only** when the project has **>= 2** selections with
|
|
11
|
-
> Otherwise the CLI skips it and prints a notice.
|
|
25
|
+
> This package is **created only** when the project has **>= 2** selections with
|
|
26
|
+
> `lang === 'js'`. Otherwise the CLI skips it and prints a notice.
|
|
@@ -1,8 +1,30 @@
|
|
|
1
1
|
{
|
|
2
|
-
"name": "@
|
|
2
|
+
"name": "@repo/shared",
|
|
3
3
|
"version": "0.1.0",
|
|
4
4
|
"private": true,
|
|
5
|
-
"description": "Shared types
|
|
6
|
-
"
|
|
7
|
-
"
|
|
5
|
+
"description": "Shared data contracts (types, enums, API URLs) for {{PROJECT_NAME}}",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"main": "./dist/index.cjs",
|
|
8
|
+
"module": "./dist/index.js",
|
|
9
|
+
"types": "./dist/index.d.ts",
|
|
10
|
+
"exports": {
|
|
11
|
+
".": {
|
|
12
|
+
"types": "./dist/index.d.ts",
|
|
13
|
+
"import": "./dist/index.js",
|
|
14
|
+
"require": "./dist/index.cjs"
|
|
15
|
+
}
|
|
16
|
+
},
|
|
17
|
+
"files": [
|
|
18
|
+
"dist"
|
|
19
|
+
],
|
|
20
|
+
"scripts": {
|
|
21
|
+
"build": "tsup",
|
|
22
|
+
"dev": "tsup --watch",
|
|
23
|
+
"typecheck": "tsc --noEmit",
|
|
24
|
+
"clean": "rm -rf dist"
|
|
25
|
+
},
|
|
26
|
+
"devDependencies": {
|
|
27
|
+
"tsup": "^8.3.5",
|
|
28
|
+
"typescript": "^5.8.3"
|
|
29
|
+
}
|
|
8
30
|
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
// @repo/shared — shared data contracts for {{PROJECT_NAME}}.
|
|
2
|
+
//
|
|
3
|
+
// Placeholder. Drop the real shared code here (types, enums, API URLs, zod
|
|
4
|
+
// schemas, etc.) and re-export it from this barrel file. Everything exported
|
|
5
|
+
// here is published to the other apps via `import { ... } from '@repo/shared'`.
|
|
6
|
+
|
|
7
|
+
export const PROJECT_NAME = '{{PROJECT_NAME}}';
|
|
8
|
+
|
|
9
|
+
export interface ApiResponse<T> {
|
|
10
|
+
data: T;
|
|
11
|
+
message?: string;
|
|
12
|
+
}
|