@emeryld/manager 0.4.4 → 0.4.6
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/dist/create-package/index.js +10 -8
- package/dist/create-package/shared.js +92 -22
- package/dist/create-package/variants/client.js +10 -5
- package/dist/create-package/variants/contract.js +26 -7
- package/dist/create-package/variants/docker.js +9 -5
- package/dist/create-package/variants/empty.js +12 -5
- package/dist/create-package/variants/fullstack.js +54 -10
- package/dist/create-package/variants/server.js +9 -5
- package/package.json +2 -3
|
@@ -4,7 +4,7 @@ import path from 'node:path';
|
|
|
4
4
|
import { stdin as input } from 'node:process';
|
|
5
5
|
import { askLine, promptSingleKey } from '../prompts.js';
|
|
6
6
|
import { colors, logGlobal } from '../utils/log.js';
|
|
7
|
-
import { SCRIPT_DESCRIPTIONS, workspaceRoot, } from './shared.js';
|
|
7
|
+
import { SCRIPT_DESCRIPTIONS, workspaceRoot, ensureWorkspaceToolingFiles, } from './shared.js';
|
|
8
8
|
import { clientVariant } from './variants/client.js';
|
|
9
9
|
import { contractVariant } from './variants/contract.js';
|
|
10
10
|
import { dockerVariant } from './variants/docker.js';
|
|
@@ -201,7 +201,7 @@ function formatVariantLines(variants, selected) {
|
|
|
201
201
|
lines.push(`${pointer}${numberLabel}. ${label} ${meta}`);
|
|
202
202
|
});
|
|
203
203
|
lines.push('');
|
|
204
|
-
lines.push(colors.dim('Use ↑/↓ (or j/k) to move, digits (1-9,0 for 10) to
|
|
204
|
+
lines.push(colors.dim('Use ↑/↓ (or j/k) to move, digits (1-9,0 for 10) to run instantly, Enter to confirm, Esc/Ctrl+C to exit.'));
|
|
205
205
|
return lines;
|
|
206
206
|
}
|
|
207
207
|
function renderInteractiveList(lines, previousLineCount) {
|
|
@@ -281,7 +281,7 @@ async function promptForVariant() {
|
|
|
281
281
|
render();
|
|
282
282
|
return;
|
|
283
283
|
}
|
|
284
|
-
if (
|
|
284
|
+
if (buffer.length === 1 && (buffer[0] === 0x0d || buffer[0] === 0x0a)) {
|
|
285
285
|
commitSelection(VARIANTS[selectedIndex]);
|
|
286
286
|
return;
|
|
287
287
|
}
|
|
@@ -307,8 +307,8 @@ async function promptForTargetDir(fallback) {
|
|
|
307
307
|
return path.resolve(workspaceRoot, normalized);
|
|
308
308
|
}
|
|
309
309
|
/**
|
|
310
|
-
*
|
|
311
|
-
*
|
|
310
|
+
* Solution #1: always build the workspace dependency graph after install.
|
|
311
|
+
* This prevents workspace-unbuilt dist/types issues for workspace:* deps.
|
|
312
312
|
*/
|
|
313
313
|
async function postCreateTasks(targetDir, options) {
|
|
314
314
|
if (options?.skipInstall) {
|
|
@@ -327,8 +327,7 @@ async function postCreateTasks(targetDir, options) {
|
|
|
327
327
|
logGlobal('Skipping build (flag).', colors.dim);
|
|
328
328
|
return;
|
|
329
329
|
}
|
|
330
|
-
// Prefer
|
|
331
|
-
// fall back to building the whole workspace, then fall back to old behavior.
|
|
330
|
+
// Prefer: build deps of the new package (pkg + its workspace deps)
|
|
332
331
|
try {
|
|
333
332
|
if (options?.pkgName) {
|
|
334
333
|
logGlobal(`Building workspace deps for ${options.pkgName}…`, colors.cyan);
|
|
@@ -339,6 +338,7 @@ async function postCreateTasks(targetDir, options) {
|
|
|
339
338
|
catch (error) {
|
|
340
339
|
logGlobal(`Filtered workspace build failed; will try full workspace build: ${error instanceof Error ? error.message : String(error)}`, colors.yellow);
|
|
341
340
|
}
|
|
341
|
+
// Fallback: build everything
|
|
342
342
|
try {
|
|
343
343
|
logGlobal('Building full workspace…', colors.cyan);
|
|
344
344
|
await runCommand('pnpm', ['-r', 'build'], workspaceRoot);
|
|
@@ -347,7 +347,7 @@ async function postCreateTasks(targetDir, options) {
|
|
|
347
347
|
catch (error) {
|
|
348
348
|
logGlobal(`Full workspace build failed; falling back to building only the new package: ${error instanceof Error ? error.message : String(error)}`, colors.yellow);
|
|
349
349
|
}
|
|
350
|
-
//
|
|
350
|
+
// Final fallback (old behavior): build only the new package if it has a build script
|
|
351
351
|
try {
|
|
352
352
|
const pkgJsonPath = path.join(targetDir, 'package.json');
|
|
353
353
|
const pkgRaw = await readFile(pkgJsonPath, 'utf8');
|
|
@@ -383,6 +383,8 @@ async function gatherTarget(initial = {}) {
|
|
|
383
383
|
export async function createRrrPackage(options = {}) {
|
|
384
384
|
const target = await gatherTarget(options);
|
|
385
385
|
logGlobal(`Creating ${target.variant.label} in ${path.relative(workspaceRoot, target.targetDir) || '.'}`, colors.green);
|
|
386
|
+
const toolingRoot = target.variant.id === 'rrr-fullstack' ? target.targetDir : workspaceRoot;
|
|
387
|
+
await ensureWorkspaceToolingFiles(toolingRoot);
|
|
386
388
|
await target.variant.scaffold({
|
|
387
389
|
targetDir: target.targetDir,
|
|
388
390
|
pkgName: target.pkgName,
|
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
import { readFileSync } from 'node:fs';
|
|
2
2
|
import { access, mkdir, writeFile } from 'node:fs/promises';
|
|
3
3
|
import path from 'node:path';
|
|
4
|
+
import { promptYesNoAll } from '../prompts.js';
|
|
4
5
|
export const workspaceRoot = process.cwd();
|
|
6
|
+
export function isWorkspaceRoot(dir) {
|
|
7
|
+
return path.resolve(dir) === path.resolve(workspaceRoot);
|
|
8
|
+
}
|
|
5
9
|
function pathExists(target) {
|
|
6
10
|
return access(target)
|
|
7
11
|
.then(() => true)
|
|
@@ -31,26 +35,45 @@ export async function writeFileIfMissing(baseDir, relative, contents) {
|
|
|
31
35
|
console.log(` created ${rel}`);
|
|
32
36
|
return 'created';
|
|
33
37
|
}
|
|
38
|
+
export async function writeFileWithPrompt(baseDir, relative, contents) {
|
|
39
|
+
const fullPath = path.join(baseDir, relative);
|
|
40
|
+
await mkdir(path.dirname(fullPath), { recursive: true });
|
|
41
|
+
const rel = path.relative(workspaceRoot, fullPath);
|
|
42
|
+
const exists = await pathExists(fullPath);
|
|
43
|
+
if (exists) {
|
|
44
|
+
const answer = await promptYesNoAll(`Overwrite existing ${rel}?`);
|
|
45
|
+
if (answer !== 'yes') {
|
|
46
|
+
console.log(` kept existing ${rel}`);
|
|
47
|
+
return 'skipped';
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
await writeFile(fullPath, contents, 'utf8');
|
|
51
|
+
console.log(` ${exists ? 'updated' : 'created'} ${rel}`);
|
|
52
|
+
return exists ? 'updated' : 'created';
|
|
53
|
+
}
|
|
34
54
|
export function baseTsConfig(options) {
|
|
35
55
|
const config = {
|
|
36
56
|
...(options?.extends ? { extends: options.extends } : {}),
|
|
37
57
|
compilerOptions: {
|
|
38
58
|
target: 'ES2020',
|
|
39
|
-
module: '
|
|
40
|
-
moduleResolution: '
|
|
41
|
-
outDir: options
|
|
42
|
-
rootDir: options
|
|
43
|
-
declaration: true,
|
|
44
|
-
sourceMap: true,
|
|
45
|
-
strict: true,
|
|
46
|
-
esModuleInterop: true,
|
|
47
|
-
skipLibCheck: true,
|
|
59
|
+
module: 'ESNext',
|
|
60
|
+
moduleResolution: 'Bundler',
|
|
61
|
+
...(options?.outDir ? { outDir: options.outDir } : {}),
|
|
62
|
+
...(options?.rootDir ? { rootDir: options.rootDir } : {}),
|
|
63
|
+
declaration: options?.declaration ?? true,
|
|
64
|
+
sourceMap: options?.sourceMap ?? true,
|
|
65
|
+
strict: options?.strict ?? true,
|
|
66
|
+
esModuleInterop: options?.esModuleInterop ?? true,
|
|
67
|
+
skipLibCheck: options?.skipLibCheck ?? true,
|
|
68
|
+
resolveJsonModule: options?.resolveJsonModule ?? true,
|
|
69
|
+
forceConsistentCasingInFileNames: options?.forceConsistentCasingInFileNames ?? true,
|
|
70
|
+
baseUrl: options?.baseUrl ?? '.',
|
|
48
71
|
lib: options?.lib,
|
|
49
72
|
types: options?.types,
|
|
50
73
|
jsx: options?.jsx,
|
|
51
74
|
},
|
|
52
|
-
include: options?.include
|
|
53
|
-
exclude: options?.exclude
|
|
75
|
+
include: options?.include,
|
|
76
|
+
exclude: options?.exclude,
|
|
54
77
|
};
|
|
55
78
|
return `${JSON.stringify(config, null, 2)}\n`;
|
|
56
79
|
}
|
|
@@ -145,7 +168,8 @@ const DEFAULT_GITIGNORE_ENTRIES = [
|
|
|
145
168
|
export function gitignoreFrom(entries = DEFAULT_GITIGNORE_ENTRIES) {
|
|
146
169
|
return entries.join('\n');
|
|
147
170
|
}
|
|
148
|
-
export function baseScripts(devCommand, extras) {
|
|
171
|
+
export function baseScripts(devCommand, extras, options) {
|
|
172
|
+
const includePrepare = options?.includePrepare ?? true;
|
|
149
173
|
return {
|
|
150
174
|
dev: devCommand,
|
|
151
175
|
build: 'tsc -p tsconfig.json',
|
|
@@ -157,18 +181,36 @@ export function baseScripts(devCommand, extras) {
|
|
|
157
181
|
'format:check': 'prettier . --check',
|
|
158
182
|
clean: 'rimraf dist .turbo coverage',
|
|
159
183
|
test: "node -e \"console.log('No tests yet')\"",
|
|
160
|
-
|
|
184
|
+
...(includePrepare
|
|
185
|
+
? { prepare: 'git rev-parse --is-inside-work-tree >/dev/null 2>&1 && husky || true' }
|
|
186
|
+
: {}),
|
|
161
187
|
...extras,
|
|
162
188
|
};
|
|
163
189
|
}
|
|
164
190
|
export function basePackageFiles(options) {
|
|
165
191
|
return {
|
|
166
|
-
'
|
|
192
|
+
'.gitignore': gitignoreFrom(options?.gitignoreEntries),
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
export async function ensureWorkspaceToolingFiles(baseDir, options) {
|
|
196
|
+
const defaultTsconfig = path.join(baseDir, 'tsconfig.json');
|
|
197
|
+
const resolvedRootConfig = options?.tsconfigPath
|
|
198
|
+
? path.resolve(baseDir, options.tsconfigPath)
|
|
199
|
+
: await resolveRootTsconfig(baseDir);
|
|
200
|
+
const tsconfigPath = toPosixPath(path.relative(baseDir, resolvedRootConfig ?? defaultTsconfig) || './tsconfig.json');
|
|
201
|
+
const files = workspaceToolingFiles(tsconfigPath);
|
|
202
|
+
for (const [relative, contents] of Object.entries(files)) {
|
|
203
|
+
// eslint-disable-next-line no-await-in-loop
|
|
204
|
+
await writeFileWithPrompt(baseDir, relative, contents);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
export function workspaceToolingFiles(tsconfigPath) {
|
|
208
|
+
return {
|
|
209
|
+
'eslint.config.js': baseEslintConfig(tsconfigPath),
|
|
167
210
|
'prettier.config.js': basePrettierConfig(),
|
|
168
211
|
'.prettierignore': `${PRETTIER_IGNORE}\n`,
|
|
169
212
|
'.vscode/settings.json': vscodeSettings(),
|
|
170
213
|
'.husky/pre-commit': HUSKY_PRE_COMMIT,
|
|
171
|
-
'.gitignore': gitignoreFrom(options?.gitignoreEntries),
|
|
172
214
|
};
|
|
173
215
|
}
|
|
174
216
|
export const SCRIPT_DESCRIPTIONS = {
|
|
@@ -261,27 +303,55 @@ function readRootPackageManager() {
|
|
|
261
303
|
return undefined;
|
|
262
304
|
}
|
|
263
305
|
}
|
|
264
|
-
async function resolveRootTsconfig() {
|
|
306
|
+
async function resolveRootTsconfig(baseDir = workspaceRoot) {
|
|
265
307
|
const candidates = ['tsconfig.base.json', 'tsconfig.json'];
|
|
266
308
|
for (const candidate of candidates) {
|
|
267
|
-
const fullPath = path.join(
|
|
309
|
+
const fullPath = path.join(baseDir, candidate);
|
|
268
310
|
if (await pathExists(fullPath)) {
|
|
269
311
|
return fullPath;
|
|
270
312
|
}
|
|
271
313
|
}
|
|
272
314
|
return undefined;
|
|
273
315
|
}
|
|
316
|
+
async function findNearestTsconfig(startDir) {
|
|
317
|
+
let current = path.resolve(startDir);
|
|
318
|
+
// eslint-disable-next-line no-constant-condition
|
|
319
|
+
while (true) {
|
|
320
|
+
const found = await resolveRootTsconfig(current);
|
|
321
|
+
if (found)
|
|
322
|
+
return found;
|
|
323
|
+
const parent = path.dirname(current);
|
|
324
|
+
if (parent === current)
|
|
325
|
+
return undefined;
|
|
326
|
+
current = parent;
|
|
327
|
+
}
|
|
328
|
+
}
|
|
274
329
|
export async function packageTsConfig(targetDir, options) {
|
|
275
330
|
const extendsFromRoot = options?.extendsFromRoot ?? true;
|
|
276
331
|
let extendsPath;
|
|
277
332
|
if (extendsFromRoot) {
|
|
278
|
-
const rootConfig = await
|
|
333
|
+
const rootConfig = await findNearestTsconfig(targetDir);
|
|
279
334
|
if (rootConfig) {
|
|
280
335
|
const relative = path.relative(targetDir, rootConfig);
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
}
|
|
336
|
+
const normalized = toPosixPath(relative || './tsconfig.base.json');
|
|
337
|
+
extendsPath = normalized.startsWith('.') ? normalized : `./${normalized}`;
|
|
284
338
|
}
|
|
285
339
|
}
|
|
286
|
-
|
|
340
|
+
const compilerOptions = stripUndefined({
|
|
341
|
+
rootDir: options?.rootDir ?? 'src',
|
|
342
|
+
outDir: options?.outDir ?? 'dist',
|
|
343
|
+
tsBuildInfoFile: 'dist/.tsbuildinfo',
|
|
344
|
+
jsx: options?.jsx,
|
|
345
|
+
types: options?.types,
|
|
346
|
+
lib: options?.lib,
|
|
347
|
+
esModuleInterop: options?.esModuleInterop ?? true,
|
|
348
|
+
allowSyntheticDefaultImports: options?.esModuleInterop ?? true,
|
|
349
|
+
skipLibCheck: options?.skipLibCheck ?? true,
|
|
350
|
+
});
|
|
351
|
+
const config = stripUndefined({
|
|
352
|
+
extends: extendsPath,
|
|
353
|
+
compilerOptions,
|
|
354
|
+
include: options?.include ?? ['src/**/*.ts'],
|
|
355
|
+
});
|
|
356
|
+
return `${JSON.stringify(config, null, 2)}\n`;
|
|
287
357
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { BASE_LINT_DEV_DEPENDENCIES, basePackageFiles, basePackageJson, buildReadme, baseScripts, packageTsConfig, writeFileIfMissing, } from '../shared.js';
|
|
1
|
+
import { BASE_LINT_DEV_DEPENDENCIES, basePackageFiles, basePackageJson, buildReadme, baseScripts, packageTsConfig, isWorkspaceRoot, writeFileIfMissing, } from '../shared.js';
|
|
2
2
|
const CLIENT_SCRIPTS = [
|
|
3
3
|
'dev',
|
|
4
4
|
'build',
|
|
@@ -29,10 +29,12 @@ export const healthGet = routeClient.build(registry.byKey['GET /api/health'])
|
|
|
29
29
|
export const healthPost = routeClient.build(registry.byKey['POST /api/health'])
|
|
30
30
|
`;
|
|
31
31
|
}
|
|
32
|
-
export function clientPackageJson(name, contractName = CONTRACT_IMPORT_PLACEHOLDER) {
|
|
32
|
+
export function clientPackageJson(name, contractName = CONTRACT_IMPORT_PLACEHOLDER, options) {
|
|
33
33
|
return basePackageJson({
|
|
34
34
|
name,
|
|
35
|
-
scripts: baseScripts('tsx watch src/index.ts'
|
|
35
|
+
scripts: baseScripts('tsx watch src/index.ts', undefined, {
|
|
36
|
+
includePrepare: options?.includePrepare,
|
|
37
|
+
}),
|
|
36
38
|
dependencies: {
|
|
37
39
|
[contractName]: 'workspace:*',
|
|
38
40
|
'@emeryld/rrroutes-client': '^2.5.3',
|
|
@@ -46,12 +48,15 @@ export function clientPackageJson(name, contractName = CONTRACT_IMPORT_PLACEHOLD
|
|
|
46
48
|
});
|
|
47
49
|
}
|
|
48
50
|
async function clientFiles(pkgName, contractImport, targetDir) {
|
|
51
|
+
const includePrepare = isWorkspaceRoot(targetDir);
|
|
49
52
|
const tsconfig = await packageTsConfig(targetDir, {
|
|
53
|
+
include: ['src/**/*.ts', 'src/**/*.tsx'],
|
|
50
54
|
lib: ['ES2020', 'DOM'],
|
|
51
|
-
types: ['
|
|
55
|
+
types: ['react', 'react-native'],
|
|
56
|
+
jsx: 'react-jsx',
|
|
52
57
|
});
|
|
53
58
|
return {
|
|
54
|
-
'package.json': clientPackageJson(pkgName, contractImport),
|
|
59
|
+
'package.json': clientPackageJson(pkgName, contractImport, { includePrepare }),
|
|
55
60
|
'tsconfig.json': tsconfig,
|
|
56
61
|
...basePackageFiles(),
|
|
57
62
|
'src/index.ts': clientIndexTs(contractImport),
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { BASE_LINT_DEV_DEPENDENCIES, basePackageFiles, basePackageJson, buildReadme, baseScripts, packageTsConfig, writeFileIfMissing, } from '../shared.js';
|
|
1
|
+
import { BASE_LINT_DEV_DEPENDENCIES, basePackageFiles, basePackageJson, buildReadme, baseScripts, packageTsConfig, isWorkspaceRoot, writeFileIfMissing, } from '../shared.js';
|
|
2
2
|
const CONTRACT_SCRIPTS = [
|
|
3
3
|
'dev',
|
|
4
4
|
'build',
|
|
@@ -10,9 +10,22 @@ const CONTRACT_SCRIPTS = [
|
|
|
10
10
|
'clean',
|
|
11
11
|
'test',
|
|
12
12
|
];
|
|
13
|
-
|
|
13
|
+
/**
|
|
14
|
+
* IMPORTANT:
|
|
15
|
+
* @emeryld/rrroutes-contract appears to ship types that may not expose these as named exports
|
|
16
|
+
* in some module shapes (CJS export= or default export). To make scaffolds compile reliably,
|
|
17
|
+
* we import as a namespace and then grab from either `default` or the namespace object.
|
|
18
|
+
*/
|
|
19
|
+
export const CONTRACT_TS = `import * as rrroutesContract from '@emeryld/rrroutes-contract'
|
|
14
20
|
import { z } from 'zod'
|
|
15
21
|
|
|
22
|
+
const api = (rrroutesContract as any).default ?? rrroutesContract
|
|
23
|
+
const { defineSocketEvents, finalize, resource } = api as {
|
|
24
|
+
defineSocketEvents: (...args: any[]) => any
|
|
25
|
+
finalize: (...args: any[]) => any
|
|
26
|
+
resource: (...args: any[]) => any
|
|
27
|
+
}
|
|
28
|
+
|
|
16
29
|
const routes = resource('/api')
|
|
17
30
|
.sub(
|
|
18
31
|
resource('health')
|
|
@@ -80,7 +93,7 @@ export const socketConfig = sockets.config
|
|
|
80
93
|
export const socketEvents = sockets.events
|
|
81
94
|
export type AppRegistry = typeof registry
|
|
82
95
|
`;
|
|
83
|
-
function contractPackageJson(name) {
|
|
96
|
+
function contractPackageJson(name, options) {
|
|
84
97
|
return basePackageJson({
|
|
85
98
|
name,
|
|
86
99
|
private: false,
|
|
@@ -90,8 +103,10 @@ function contractPackageJson(name) {
|
|
|
90
103
|
import: './dist/index.js',
|
|
91
104
|
},
|
|
92
105
|
},
|
|
93
|
-
// ✅
|
|
94
|
-
scripts: baseScripts('tsc -p tsconfig.json --watch --preserveWatchOutput'),
|
|
106
|
+
// ✅ Solution #2: dev continuously emits dist/*.js + dist/*.d.ts
|
|
107
|
+
scripts: baseScripts('tsc -p tsconfig.json --watch --preserveWatchOutput', undefined, { includePrepare: options?.includePrepare }),
|
|
108
|
+
// You can keep this pinned if you want, but the import strategy above prevents breakage
|
|
109
|
+
// across different module export shapes.
|
|
95
110
|
dependencies: {
|
|
96
111
|
'@emeryld/rrroutes-contract': '^2.5.2',
|
|
97
112
|
zod: '^4.2.1',
|
|
@@ -102,9 +117,13 @@ function contractPackageJson(name) {
|
|
|
102
117
|
});
|
|
103
118
|
}
|
|
104
119
|
async function contractFiles(pkgName, targetDir) {
|
|
105
|
-
const
|
|
120
|
+
const includePrepare = isWorkspaceRoot(targetDir);
|
|
121
|
+
const tsconfig = await packageTsConfig(targetDir, {
|
|
122
|
+
include: ['src/**/*.ts', 'src/**/*.tsx'],
|
|
123
|
+
skipLibCheck: true,
|
|
124
|
+
});
|
|
106
125
|
return {
|
|
107
|
-
'package.json': contractPackageJson(pkgName),
|
|
126
|
+
'package.json': contractPackageJson(pkgName, { includePrepare }),
|
|
108
127
|
'tsconfig.json': tsconfig,
|
|
109
128
|
...basePackageFiles(),
|
|
110
129
|
'src/index.ts': CONTRACT_TS,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { BASE_LINT_DEV_DEPENDENCIES, basePackageFiles, basePackageJson, buildReadme, baseScripts, packageTsConfig, writeFileIfMissing, } from '../shared.js';
|
|
1
|
+
import { BASE_LINT_DEV_DEPENDENCIES, basePackageFiles, basePackageJson, buildReadme, baseScripts, packageTsConfig, isWorkspaceRoot, writeFileIfMissing, } from '../shared.js';
|
|
2
2
|
const DOCKER_SCRIPTS = [
|
|
3
3
|
'dev',
|
|
4
4
|
'build',
|
|
@@ -18,7 +18,7 @@ const DOCKER_SCRIPTS = [
|
|
|
18
18
|
'docker:clean',
|
|
19
19
|
'docker:reset',
|
|
20
20
|
];
|
|
21
|
-
function dockerPackageJson(name) {
|
|
21
|
+
function dockerPackageJson(name, options) {
|
|
22
22
|
return basePackageJson({
|
|
23
23
|
name,
|
|
24
24
|
scripts: baseScripts('tsx watch src/index.ts', {
|
|
@@ -31,7 +31,7 @@ function dockerPackageJson(name) {
|
|
|
31
31
|
'docker:stop': 'npm run docker:cli -- stop',
|
|
32
32
|
'docker:clean': 'npm run docker:cli -- clean',
|
|
33
33
|
'docker:reset': 'npm run docker:cli -- reset',
|
|
34
|
-
}),
|
|
34
|
+
}, { includePrepare: options?.includePrepare }),
|
|
35
35
|
dependencies: {
|
|
36
36
|
cors: '^2.8.5',
|
|
37
37
|
express: '^5.1.0',
|
|
@@ -96,9 +96,13 @@ CMD ["node", "dist/index.js"]
|
|
|
96
96
|
`;
|
|
97
97
|
}
|
|
98
98
|
async function dockerFiles(pkgName, targetDir) {
|
|
99
|
-
const
|
|
99
|
+
const includePrepare = isWorkspaceRoot(targetDir);
|
|
100
|
+
const tsconfig = await packageTsConfig(targetDir, {
|
|
101
|
+
include: ['src/**/*.ts'],
|
|
102
|
+
types: ['node'],
|
|
103
|
+
});
|
|
100
104
|
return {
|
|
101
|
-
'package.json': dockerPackageJson(pkgName),
|
|
105
|
+
'package.json': dockerPackageJson(pkgName, { includePrepare }),
|
|
102
106
|
'tsconfig.json': tsconfig,
|
|
103
107
|
'src/index.ts': dockerIndexTs(),
|
|
104
108
|
'scripts/docker.ts': dockerCliScript(pkgName),
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { BASE_LINT_DEV_DEPENDENCIES, basePackageFiles, basePackageJson, buildReadme, baseScripts, packageTsConfig, writeFileIfMissing, } from '../shared.js';
|
|
1
|
+
import { BASE_LINT_DEV_DEPENDENCIES, basePackageFiles, basePackageJson, buildReadme, baseScripts, packageTsConfig, isWorkspaceRoot, writeFileIfMissing, } from '../shared.js';
|
|
2
2
|
const EMPTY_SCRIPTS = [
|
|
3
3
|
'dev',
|
|
4
4
|
'build',
|
|
@@ -10,19 +10,26 @@ const EMPTY_SCRIPTS = [
|
|
|
10
10
|
'clean',
|
|
11
11
|
'test',
|
|
12
12
|
];
|
|
13
|
-
function emptyPackageJson(name) {
|
|
13
|
+
function emptyPackageJson(name, options) {
|
|
14
14
|
return basePackageJson({
|
|
15
15
|
name,
|
|
16
|
-
scripts: baseScripts('tsx watch src/index.ts'
|
|
16
|
+
scripts: baseScripts('tsx watch src/index.ts', undefined, {
|
|
17
|
+
includePrepare: options?.includePrepare,
|
|
18
|
+
}),
|
|
17
19
|
devDependencies: {
|
|
18
20
|
...BASE_LINT_DEV_DEPENDENCIES,
|
|
19
21
|
},
|
|
20
22
|
});
|
|
21
23
|
}
|
|
22
24
|
async function emptyFiles(pkgName, targetDir) {
|
|
23
|
-
const
|
|
25
|
+
const includePrepare = isWorkspaceRoot(targetDir);
|
|
26
|
+
const tsconfig = await packageTsConfig(targetDir, {
|
|
27
|
+
include: ['src/**/*.ts', 'src/**/*.tsx'],
|
|
28
|
+
types: ['node'],
|
|
29
|
+
skipLibCheck: true,
|
|
30
|
+
});
|
|
24
31
|
return {
|
|
25
|
-
'package.json': emptyPackageJson(pkgName),
|
|
32
|
+
'package.json': emptyPackageJson(pkgName, { includePrepare }),
|
|
26
33
|
'tsconfig.json': tsconfig,
|
|
27
34
|
...basePackageFiles(),
|
|
28
35
|
'src/index.ts': "export const hello = 'world'\n",
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import path from 'node:path';
|
|
2
|
-
import { BASE_LINT_DEV_DEPENDENCIES, basePackageFiles, basePackageJson,
|
|
2
|
+
import { BASE_LINT_DEV_DEPENDENCIES, basePackageFiles, basePackageJson, writeFileIfMissing, } from '../shared.js';
|
|
3
3
|
import { clientVariant } from './client.js';
|
|
4
4
|
import { serverVariant } from './server.js';
|
|
5
5
|
import { dockerVariant } from './docker.js';
|
|
@@ -43,6 +43,9 @@ function deriveDirs(rootDir, baseName) {
|
|
|
43
43
|
docker: path.join(packagesRoot, `${baseName}-docker`),
|
|
44
44
|
};
|
|
45
45
|
}
|
|
46
|
+
function toPosixPath(value) {
|
|
47
|
+
return value.split(path.sep).join('/');
|
|
48
|
+
}
|
|
46
49
|
function rootPackageJson(baseName) {
|
|
47
50
|
const dockerPackageDir = `packages/${baseName}-docker`;
|
|
48
51
|
return basePackageJson({
|
|
@@ -50,7 +53,7 @@ function rootPackageJson(baseName) {
|
|
|
50
53
|
private: true,
|
|
51
54
|
useDefaults: false,
|
|
52
55
|
scripts: {
|
|
53
|
-
setup: 'pnpm install && pnpm exec husky
|
|
56
|
+
setup: 'pnpm install && (git rev-parse --is-inside-work-tree >/dev/null 2>&1 && pnpm exec husky || true)',
|
|
54
57
|
dev: 'pnpm -r dev --parallel --if-present',
|
|
55
58
|
build: 'pnpm -r build',
|
|
56
59
|
typecheck: 'pnpm -r typecheck',
|
|
@@ -61,7 +64,7 @@ function rootPackageJson(baseName) {
|
|
|
61
64
|
'format:check': 'pnpm -r format:check --if-present',
|
|
62
65
|
test: 'pnpm -r test --if-present',
|
|
63
66
|
clean: 'pnpm -r clean --if-present && rimraf node_modules .turbo coverage',
|
|
64
|
-
prepare: 'husky
|
|
67
|
+
prepare: 'git rev-parse --is-inside-work-tree >/dev/null 2>&1 && husky || true',
|
|
65
68
|
'docker:up': `pnpm -C ${dockerPackageDir} run docker:up`,
|
|
66
69
|
'docker:dev': `pnpm -C ${dockerPackageDir} run docker:dev`,
|
|
67
70
|
'docker:logs': `pnpm -C ${dockerPackageDir} run docker:logs`,
|
|
@@ -77,6 +80,50 @@ function rootPackageJson(baseName) {
|
|
|
77
80
|
function rootPnpmWorkspace() {
|
|
78
81
|
return "packages:\n - 'packages/*'\n";
|
|
79
82
|
}
|
|
83
|
+
function rootTsconfigBase(baseName, names) {
|
|
84
|
+
const paths = {
|
|
85
|
+
[names.contract]: [`packages/${baseName}-contract/src`],
|
|
86
|
+
[`${names.contract}/*`]: [`packages/${baseName}-contract/src/*`],
|
|
87
|
+
[names.server]: [`packages/${baseName}-server/src`],
|
|
88
|
+
[`${names.server}/*`]: [`packages/${baseName}-server/src/*`],
|
|
89
|
+
[names.client]: [`packages/${baseName}-client/src`],
|
|
90
|
+
[`${names.client}/*`]: [`packages/${baseName}-client/src/*`],
|
|
91
|
+
[names.docker]: [`packages/${baseName}-docker/src`],
|
|
92
|
+
[`${names.docker}/*`]: [`packages/${baseName}-docker/src/*`],
|
|
93
|
+
};
|
|
94
|
+
const config = {
|
|
95
|
+
$schema: 'https://json.schemastore.org/tsconfig',
|
|
96
|
+
compilerOptions: {
|
|
97
|
+
target: 'ES2020',
|
|
98
|
+
module: 'ESNext',
|
|
99
|
+
moduleResolution: 'Bundler',
|
|
100
|
+
jsx: 'react-jsx',
|
|
101
|
+
strict: true,
|
|
102
|
+
skipLibCheck: true,
|
|
103
|
+
resolveJsonModule: true,
|
|
104
|
+
forceConsistentCasingInFileNames: true,
|
|
105
|
+
sourceMap: true,
|
|
106
|
+
baseUrl: '.',
|
|
107
|
+
paths,
|
|
108
|
+
},
|
|
109
|
+
};
|
|
110
|
+
return `${JSON.stringify(config, null, 2)}\n`;
|
|
111
|
+
}
|
|
112
|
+
function rootSolutionTsconfig(dirs) {
|
|
113
|
+
const references = [
|
|
114
|
+
dirs.contract,
|
|
115
|
+
dirs.server,
|
|
116
|
+
dirs.client,
|
|
117
|
+
dirs.docker,
|
|
118
|
+
].map((pkgDir) => ({ path: toPosixPath(path.relative(dirs.root, pkgDir)) }));
|
|
119
|
+
const config = {
|
|
120
|
+
$schema: 'https://json.schemastore.org/tsconfig',
|
|
121
|
+
extends: './tsconfig.base.json',
|
|
122
|
+
files: [],
|
|
123
|
+
references,
|
|
124
|
+
};
|
|
125
|
+
return `${JSON.stringify(config, null, 2)}\n`;
|
|
126
|
+
}
|
|
80
127
|
function stackComposeYaml() {
|
|
81
128
|
return `services:
|
|
82
129
|
db:
|
|
@@ -95,16 +142,13 @@ volumes:
|
|
|
95
142
|
db-data:
|
|
96
143
|
`;
|
|
97
144
|
}
|
|
98
|
-
async function scaffoldRootFiles(baseDir, baseName) {
|
|
145
|
+
async function scaffoldRootFiles(baseDir, baseName, names, dirs) {
|
|
99
146
|
const files = {
|
|
100
147
|
'package.json': rootPackageJson(baseName),
|
|
101
148
|
'pnpm-workspace.yaml': rootPnpmWorkspace(),
|
|
102
149
|
'docker-compose.yml': stackComposeYaml(),
|
|
103
|
-
'tsconfig.json':
|
|
104
|
-
|
|
105
|
-
outDir: 'dist',
|
|
106
|
-
include: ['packages/**/*', 'scripts/**/*', 'src/**/*'],
|
|
107
|
-
}),
|
|
150
|
+
'tsconfig.base.json': rootTsconfigBase(baseName, names),
|
|
151
|
+
'tsconfig.json': rootSolutionTsconfig(dirs),
|
|
108
152
|
...basePackageFiles(),
|
|
109
153
|
};
|
|
110
154
|
for (const [relative, contents] of Object.entries(files)) {
|
|
@@ -136,7 +180,7 @@ export const fullstackVariant = {
|
|
|
136
180
|
const names = deriveNames(baseName);
|
|
137
181
|
const dirs = deriveDirs(ctx.targetDir, baseName);
|
|
138
182
|
// Root workspace files
|
|
139
|
-
await scaffoldRootFiles(dirs.root, baseName);
|
|
183
|
+
await scaffoldRootFiles(dirs.root, baseName, names, dirs);
|
|
140
184
|
// Contract package (reuse contract variant)
|
|
141
185
|
await contractVariant.scaffold({
|
|
142
186
|
targetDir: dirs.contract,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { BASE_LINT_DEV_DEPENDENCIES, basePackageFiles, basePackageJson, buildReadme, baseScripts, packageTsConfig, writeFileIfMissing, } from '../shared.js';
|
|
1
|
+
import { BASE_LINT_DEV_DEPENDENCIES, basePackageFiles, basePackageJson, buildReadme, baseScripts, packageTsConfig, isWorkspaceRoot, writeFileIfMissing, } from '../shared.js';
|
|
2
2
|
const SERVER_SCRIPTS = [
|
|
3
3
|
'dev',
|
|
4
4
|
'build',
|
|
@@ -58,13 +58,13 @@ server.listen(PORT, () => {
|
|
|
58
58
|
})
|
|
59
59
|
`;
|
|
60
60
|
}
|
|
61
|
-
export function serverPackageJson(name, contractName = CONTRACT_IMPORT_PLACEHOLDER) {
|
|
61
|
+
export function serverPackageJson(name, contractName = CONTRACT_IMPORT_PLACEHOLDER, options) {
|
|
62
62
|
return basePackageJson({
|
|
63
63
|
name,
|
|
64
64
|
private: false,
|
|
65
65
|
scripts: baseScripts('tsx watch --env-file .env src/index.ts', {
|
|
66
66
|
start: 'node dist/index.js',
|
|
67
|
-
}),
|
|
67
|
+
}, { includePrepare: options?.includePrepare }),
|
|
68
68
|
dependencies: {
|
|
69
69
|
[contractName]: 'workspace:*',
|
|
70
70
|
'@emeryld/rrroutes-server': '^2.4.1',
|
|
@@ -82,9 +82,13 @@ export function serverPackageJson(name, contractName = CONTRACT_IMPORT_PLACEHOLD
|
|
|
82
82
|
});
|
|
83
83
|
}
|
|
84
84
|
async function serverFiles(pkgName, contractImport, targetDir) {
|
|
85
|
-
const
|
|
85
|
+
const includePrepare = isWorkspaceRoot(targetDir);
|
|
86
|
+
const tsconfig = await packageTsConfig(targetDir, {
|
|
87
|
+
include: ['src/**/*.ts'],
|
|
88
|
+
types: ['node'],
|
|
89
|
+
});
|
|
86
90
|
return {
|
|
87
|
-
'package.json': serverPackageJson(pkgName, contractImport),
|
|
91
|
+
'package.json': serverPackageJson(pkgName, contractImport, { includePrepare }),
|
|
88
92
|
'tsconfig.json': tsconfig,
|
|
89
93
|
...basePackageFiles(),
|
|
90
94
|
'src/index.ts': serverIndexTs(contractImport),
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@emeryld/manager",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.6",
|
|
4
4
|
"description": "Interactive manager for pnpm monorepos (update/test/build/publish).",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -30,8 +30,7 @@
|
|
|
30
30
|
"devDependencies": {
|
|
31
31
|
"@types/node": "^20.17.0",
|
|
32
32
|
"@types/semver": "^7.7.1",
|
|
33
|
-
"cross-env": "^7.0.3"
|
|
34
|
-
"@emeryld/manager": "^0.4.1"
|
|
33
|
+
"cross-env": "^7.0.3"
|
|
35
34
|
},
|
|
36
35
|
"ts-node": {
|
|
37
36
|
"esm": true,
|