@emeryld/manager 0.2.3 → 0.3.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/bin/manager-cli.js +32 -14
- package/dist/create-package.js +388 -0
- package/dist/manager-cli.js +32 -14
- package/dist/menu.js +2 -0
- package/dist/packages.js +1 -1
- package/dist/publish.js +24 -5
- package/package.json +1 -1
package/bin/manager-cli.js
CHANGED
|
@@ -1,28 +1,46 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { spawnSync } from 'node:child_process'
|
|
3
|
+
import { existsSync } from 'node:fs'
|
|
3
4
|
import path from 'node:path'
|
|
4
|
-
import { fileURLToPath
|
|
5
|
+
import { fileURLToPath } from 'node:url'
|
|
5
6
|
import { createRequire } from 'node:module'
|
|
6
7
|
|
|
7
8
|
const __filename = fileURLToPath(import.meta.url)
|
|
8
9
|
const __dirname = path.dirname(__filename)
|
|
9
10
|
const packageRoot = path.resolve(__dirname, '..')
|
|
10
11
|
const tsconfigPath = path.join(packageRoot, 'tsconfig.base.json')
|
|
11
|
-
const
|
|
12
|
+
const compiledEntry = path.join(packageRoot, 'dist', 'publish.js')
|
|
13
|
+
const sourceEntry = path.join(packageRoot, 'src', 'publish.ts')
|
|
14
|
+
const args = process.argv.slice(2)
|
|
12
15
|
|
|
13
|
-
const
|
|
14
|
-
const
|
|
15
|
-
const registerCode = [
|
|
16
|
-
'import { register } from "node:module";',
|
|
17
|
-
'import { pathToFileURL } from "node:url";',
|
|
18
|
-
`register(${JSON.stringify(tsNodeLoader)}, pathToFileURL(${JSON.stringify(packageRoot + path.sep)}));`,
|
|
19
|
-
].join(' ')
|
|
20
|
-
const registerImport = `data:text/javascript,${encodeURIComponent(registerCode)}`
|
|
16
|
+
const hasCompiledEntry = existsSync(compiledEntry)
|
|
17
|
+
const hasSourceEntry = existsSync(sourceEntry)
|
|
21
18
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
19
|
+
let nodeArgs
|
|
20
|
+
let execOptions
|
|
21
|
+
|
|
22
|
+
if (hasCompiledEntry) {
|
|
23
|
+
nodeArgs = [compiledEntry, ...args]
|
|
24
|
+
execOptions = { env: process.env, stdio: 'inherit' }
|
|
25
|
+
} else if (hasSourceEntry) {
|
|
26
|
+
const require = createRequire(import.meta.url)
|
|
27
|
+
const tsNodeLoader = require.resolve('ts-node/esm.mjs')
|
|
28
|
+
const registerCode = [
|
|
29
|
+
'import { register } from "node:module";',
|
|
30
|
+
'import { pathToFileURL } from "node:url";',
|
|
31
|
+
`register(${JSON.stringify(tsNodeLoader)}, pathToFileURL(${JSON.stringify(packageRoot + path.sep)}));`,
|
|
32
|
+
].join(' ')
|
|
33
|
+
const registerImport = `data:text/javascript,${encodeURIComponent(registerCode)}`
|
|
34
|
+
nodeArgs = ['--import', registerImport, sourceEntry, ...args]
|
|
35
|
+
execOptions = {
|
|
36
|
+
env: { ...process.env, TS_NODE_PROJECT: tsconfigPath },
|
|
37
|
+
stdio: 'inherit',
|
|
38
|
+
}
|
|
39
|
+
} else {
|
|
40
|
+
console.error(
|
|
41
|
+
`manager-cli could not find a publish entry point (checked ${compiledEntry} and ${sourceEntry}).`,
|
|
42
|
+
)
|
|
43
|
+
process.exit(1)
|
|
26
44
|
}
|
|
27
45
|
|
|
28
46
|
const child = spawnSync(process.execPath, nodeArgs, execOptions)
|
|
@@ -0,0 +1,388 @@
|
|
|
1
|
+
import { access, mkdir, readdir, stat, writeFile } from 'node:fs/promises';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { askLine, promptSingleKey } from './prompts.js';
|
|
4
|
+
import { colors, logGlobal } from './utils/log.js';
|
|
5
|
+
const VARIANTS = [
|
|
6
|
+
{ id: 'rrr-contract', label: 'rrr contract', defaultDir: 'packages/rrr-contract' },
|
|
7
|
+
{ id: 'rrr-server', label: 'rrr server', defaultDir: 'packages/rrr-server' },
|
|
8
|
+
{ id: 'rrr-client', label: 'rrr client', defaultDir: 'packages/rrr-client' },
|
|
9
|
+
];
|
|
10
|
+
const workspaceRoot = process.cwd();
|
|
11
|
+
function derivePackageName(targetDir) {
|
|
12
|
+
const base = path.basename(targetDir) || 'rrr-package';
|
|
13
|
+
return base;
|
|
14
|
+
}
|
|
15
|
+
async function ensureTargetDir(targetDir) {
|
|
16
|
+
try {
|
|
17
|
+
const stats = await stat(targetDir);
|
|
18
|
+
if (!stats.isDirectory()) {
|
|
19
|
+
throw new Error(`Target "${targetDir}" exists and is not a directory.`);
|
|
20
|
+
}
|
|
21
|
+
const entries = await readdir(targetDir);
|
|
22
|
+
if (entries.length > 0) {
|
|
23
|
+
logGlobal(`Target ${path.relative(workspaceRoot, targetDir)} is not empty; existing files will be preserved.`, colors.yellow);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
catch (error) {
|
|
27
|
+
if (error &&
|
|
28
|
+
typeof error === 'object' &&
|
|
29
|
+
error.code === 'ENOENT') {
|
|
30
|
+
await mkdir(targetDir, { recursive: true });
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
throw error;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
async function writeFileIfMissing(baseDir, relative, contents) {
|
|
37
|
+
const fullPath = path.join(baseDir, relative);
|
|
38
|
+
await mkdir(path.dirname(fullPath), { recursive: true });
|
|
39
|
+
try {
|
|
40
|
+
await access(fullPath);
|
|
41
|
+
const rel = path.relative(workspaceRoot, fullPath);
|
|
42
|
+
console.log(` skipped ${rel} (already exists)`);
|
|
43
|
+
return 'skipped';
|
|
44
|
+
}
|
|
45
|
+
catch (error) {
|
|
46
|
+
if (error &&
|
|
47
|
+
typeof error === 'object' &&
|
|
48
|
+
error.code !== 'ENOENT') {
|
|
49
|
+
throw error;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
await writeFile(fullPath, contents, 'utf8');
|
|
53
|
+
const rel = path.relative(workspaceRoot, fullPath);
|
|
54
|
+
console.log(` created ${rel}`);
|
|
55
|
+
return 'created';
|
|
56
|
+
}
|
|
57
|
+
function contractIndexTs() {
|
|
58
|
+
return `import { defineSocketEvents, finalize, resource } from '@emeryld/rrroutes-contract'
|
|
59
|
+
import { z } from 'zod'
|
|
60
|
+
|
|
61
|
+
const routes = resource('/api')
|
|
62
|
+
.sub(
|
|
63
|
+
resource('health')
|
|
64
|
+
.get({
|
|
65
|
+
outputSchema: z.object({
|
|
66
|
+
status: z.literal('ok'),
|
|
67
|
+
html: z.string().optional(),
|
|
68
|
+
}),
|
|
69
|
+
description: 'Basic GET health probe for uptime + docs.',
|
|
70
|
+
})
|
|
71
|
+
.post({
|
|
72
|
+
bodySchema: z.object({
|
|
73
|
+
echo: z.string().optional(),
|
|
74
|
+
}),
|
|
75
|
+
outputSchema: z.object({
|
|
76
|
+
status: z.literal('ok'),
|
|
77
|
+
received: z.string().optional(),
|
|
78
|
+
}),
|
|
79
|
+
description: 'POST health probe that echoes a payload.',
|
|
80
|
+
})
|
|
81
|
+
.done(),
|
|
82
|
+
)
|
|
83
|
+
.done()
|
|
84
|
+
|
|
85
|
+
export const registry = finalize(routes)
|
|
86
|
+
|
|
87
|
+
const sockets = defineSocketEvents(
|
|
88
|
+
{
|
|
89
|
+
joinMetaMessage: z.object({ room: z.string().optional() }),
|
|
90
|
+
leaveMetaMessage: z.object({ room: z.string().optional() }),
|
|
91
|
+
pingPayload: z.object({
|
|
92
|
+
note: z.string().default('ping'),
|
|
93
|
+
sentAt: z.string(),
|
|
94
|
+
}),
|
|
95
|
+
pongPayload: z.object({
|
|
96
|
+
ok: z.boolean(),
|
|
97
|
+
receivedAt: z.string(),
|
|
98
|
+
echo: z.string().optional(),
|
|
99
|
+
}),
|
|
100
|
+
},
|
|
101
|
+
{
|
|
102
|
+
'health:connected': {
|
|
103
|
+
message: z.object({
|
|
104
|
+
socketId: z.string(),
|
|
105
|
+
at: z.string(),
|
|
106
|
+
message: z.string(),
|
|
107
|
+
}),
|
|
108
|
+
},
|
|
109
|
+
'health:ping': {
|
|
110
|
+
message: z.object({
|
|
111
|
+
note: z.string().default('ping'),
|
|
112
|
+
}),
|
|
113
|
+
},
|
|
114
|
+
'health:pong': {
|
|
115
|
+
message: z.object({
|
|
116
|
+
ok: z.literal(true),
|
|
117
|
+
at: z.string(),
|
|
118
|
+
echo: z.string().optional(),
|
|
119
|
+
}),
|
|
120
|
+
},
|
|
121
|
+
},
|
|
122
|
+
)
|
|
123
|
+
|
|
124
|
+
export const socketConfig = sockets.config
|
|
125
|
+
export const socketEvents = sockets.events
|
|
126
|
+
export type AppRegistry = typeof registry
|
|
127
|
+
`;
|
|
128
|
+
}
|
|
129
|
+
function serverIndexTs(contractImport) {
|
|
130
|
+
return `import 'dotenv/config'
|
|
131
|
+
import http from 'node:http'
|
|
132
|
+
import express from 'express'
|
|
133
|
+
import cors from 'cors'
|
|
134
|
+
import { createRRRoute } from '@emeryld/rrroutes-server'
|
|
135
|
+
import { registry } from '${contractImport}'
|
|
136
|
+
|
|
137
|
+
const app = express()
|
|
138
|
+
app.use(cors({ origin: '*', credentials: true }))
|
|
139
|
+
app.use(express.json())
|
|
140
|
+
|
|
141
|
+
app.get('/', (_req, res) => {
|
|
142
|
+
res.send('<h1>rrr server ready</h1>')
|
|
143
|
+
})
|
|
144
|
+
|
|
145
|
+
const routes = createRRRoute(app, {
|
|
146
|
+
buildCtx: async () => ({
|
|
147
|
+
requestId: Math.random().toString(36).slice(2),
|
|
148
|
+
}),
|
|
149
|
+
debug:
|
|
150
|
+
process.env.NODE_ENV === 'development'
|
|
151
|
+
? { request: true, handler: true }
|
|
152
|
+
: undefined,
|
|
153
|
+
})
|
|
154
|
+
|
|
155
|
+
routes.registerControllers(registry, {
|
|
156
|
+
'GET /api/health': {
|
|
157
|
+
handler: async ({ ctx }) => ({
|
|
158
|
+
out: {
|
|
159
|
+
status: 'ok',
|
|
160
|
+
requestId: ctx.requestId,
|
|
161
|
+
at: new Date().toISOString(),
|
|
162
|
+
},
|
|
163
|
+
}),
|
|
164
|
+
},
|
|
165
|
+
})
|
|
166
|
+
|
|
167
|
+
const PORT = Number.parseInt(process.env.PORT ?? '4000', 10)
|
|
168
|
+
const server = http.createServer(app)
|
|
169
|
+
|
|
170
|
+
server.listen(PORT, () => {
|
|
171
|
+
console.log(\`rrr server listening on http://localhost:\${PORT}\`)
|
|
172
|
+
})
|
|
173
|
+
`;
|
|
174
|
+
}
|
|
175
|
+
function clientIndexTs(contractImport) {
|
|
176
|
+
return `import { QueryClient } from '@tanstack/react-query'
|
|
177
|
+
import { createRouteClient } from '@emeryld/rrroutes-client'
|
|
178
|
+
import { registry } from '${contractImport}'
|
|
179
|
+
|
|
180
|
+
const baseUrl = process.env.RRR_API_URL ?? 'http://localhost:4000'
|
|
181
|
+
export const queryClient = new QueryClient()
|
|
182
|
+
|
|
183
|
+
export const routeClient = createRouteClient({
|
|
184
|
+
baseUrl,
|
|
185
|
+
queryClient,
|
|
186
|
+
environment: process.env.NODE_ENV === 'production' ? 'production' : 'development',
|
|
187
|
+
})
|
|
188
|
+
|
|
189
|
+
export const healthGet = routeClient.build(registry.byKey['GET /api/health'])
|
|
190
|
+
export const healthPost = routeClient.build(registry.byKey['POST /api/health'])
|
|
191
|
+
`;
|
|
192
|
+
}
|
|
193
|
+
function baseTsConfig(options) {
|
|
194
|
+
return `${JSON.stringify({
|
|
195
|
+
compilerOptions: {
|
|
196
|
+
target: 'ES2020',
|
|
197
|
+
module: 'NodeNext',
|
|
198
|
+
moduleResolution: 'NodeNext',
|
|
199
|
+
outDir: 'dist',
|
|
200
|
+
rootDir: 'src',
|
|
201
|
+
declaration: true,
|
|
202
|
+
sourceMap: true,
|
|
203
|
+
strict: true,
|
|
204
|
+
esModuleInterop: true,
|
|
205
|
+
skipLibCheck: true,
|
|
206
|
+
lib: options?.lib,
|
|
207
|
+
types: options?.types,
|
|
208
|
+
},
|
|
209
|
+
include: ['src/**/*'],
|
|
210
|
+
}, null, 2)}\n`;
|
|
211
|
+
}
|
|
212
|
+
function contractPackageJson(name) {
|
|
213
|
+
return `${JSON.stringify({
|
|
214
|
+
name,
|
|
215
|
+
version: '0.1.0',
|
|
216
|
+
private: false,
|
|
217
|
+
type: 'module',
|
|
218
|
+
main: 'dist/index.js',
|
|
219
|
+
types: 'dist/index.d.ts',
|
|
220
|
+
exports: {
|
|
221
|
+
'.': {
|
|
222
|
+
types: './dist/index.d.ts',
|
|
223
|
+
import: './dist/index.js',
|
|
224
|
+
},
|
|
225
|
+
},
|
|
226
|
+
files: ['dist'],
|
|
227
|
+
scripts: {
|
|
228
|
+
build: 'tsc -p tsconfig.json',
|
|
229
|
+
typecheck: 'tsc -p tsconfig.json --noEmit',
|
|
230
|
+
},
|
|
231
|
+
dependencies: {
|
|
232
|
+
'@emeryld/rrroutes-contract': '^2.5.2',
|
|
233
|
+
zod: '^4.2.1',
|
|
234
|
+
},
|
|
235
|
+
devDependencies: {
|
|
236
|
+
typescript: '^5.9.3',
|
|
237
|
+
},
|
|
238
|
+
}, null, 2)}\n`;
|
|
239
|
+
}
|
|
240
|
+
function serverPackageJson(name) {
|
|
241
|
+
return `${JSON.stringify({
|
|
242
|
+
name,
|
|
243
|
+
version: '0.1.0',
|
|
244
|
+
private: false,
|
|
245
|
+
type: 'module',
|
|
246
|
+
main: 'dist/index.js',
|
|
247
|
+
types: 'dist/index.d.ts',
|
|
248
|
+
files: ['dist'],
|
|
249
|
+
scripts: {
|
|
250
|
+
dev: 'node --loader ts-node/esm src/index.ts',
|
|
251
|
+
build: 'tsc -p tsconfig.json',
|
|
252
|
+
typecheck: 'tsc -p tsconfig.json --noEmit',
|
|
253
|
+
start: 'node dist/index.js',
|
|
254
|
+
},
|
|
255
|
+
dependencies: {
|
|
256
|
+
'@emeryld/rrroutes-contract': '^2.5.2',
|
|
257
|
+
'@emeryld/rrroutes-server': '^2.4.1',
|
|
258
|
+
cors: '^2.8.5',
|
|
259
|
+
dotenv: '^16.4.5',
|
|
260
|
+
express: '^5.1.0',
|
|
261
|
+
zod: '^4.2.1',
|
|
262
|
+
},
|
|
263
|
+
devDependencies: {
|
|
264
|
+
'@types/cors': '^2.8.5',
|
|
265
|
+
'@types/express': '^5.0.6',
|
|
266
|
+
'@types/node': '^24.10.2',
|
|
267
|
+
'ts-node': '^10.9.2',
|
|
268
|
+
typescript: '^5.9.3',
|
|
269
|
+
},
|
|
270
|
+
}, null, 2)}\n`;
|
|
271
|
+
}
|
|
272
|
+
function clientPackageJson(name) {
|
|
273
|
+
return `${JSON.stringify({
|
|
274
|
+
name,
|
|
275
|
+
version: '0.1.0',
|
|
276
|
+
private: true,
|
|
277
|
+
type: 'module',
|
|
278
|
+
main: 'dist/index.js',
|
|
279
|
+
types: 'dist/index.d.ts',
|
|
280
|
+
files: ['dist'],
|
|
281
|
+
scripts: {
|
|
282
|
+
build: 'tsc -p tsconfig.json',
|
|
283
|
+
typecheck: 'tsc -p tsconfig.json --noEmit',
|
|
284
|
+
},
|
|
285
|
+
dependencies: {
|
|
286
|
+
'@emeryld/rrroutes-client': '^2.5.3',
|
|
287
|
+
'@emeryld/rrroutes-contract': '^2.5.2',
|
|
288
|
+
'@tanstack/react-query': '^5.90.12',
|
|
289
|
+
'socket.io-client': '^4.8.3',
|
|
290
|
+
},
|
|
291
|
+
devDependencies: {
|
|
292
|
+
'@types/node': '^24.10.2',
|
|
293
|
+
typescript: '^5.9.3',
|
|
294
|
+
},
|
|
295
|
+
}, null, 2)}\n`;
|
|
296
|
+
}
|
|
297
|
+
function contractFiles(pkgName) {
|
|
298
|
+
return {
|
|
299
|
+
'package.json': contractPackageJson(pkgName),
|
|
300
|
+
'tsconfig.json': baseTsConfig(),
|
|
301
|
+
'src/index.ts': contractIndexTs(),
|
|
302
|
+
'README.md': `# ${pkgName}
|
|
303
|
+
|
|
304
|
+
Contract package scaffolded by manager-cli.
|
|
305
|
+
- edit src/index.ts to add routes and socket events
|
|
306
|
+
- build with \`npm run build\`
|
|
307
|
+
- import the registry in your server/client packages
|
|
308
|
+
`,
|
|
309
|
+
};
|
|
310
|
+
}
|
|
311
|
+
function serverFiles(pkgName, contractImport) {
|
|
312
|
+
return {
|
|
313
|
+
'package.json': serverPackageJson(pkgName),
|
|
314
|
+
'tsconfig.json': baseTsConfig({ types: ['node'] }),
|
|
315
|
+
'src/index.ts': serverIndexTs(contractImport),
|
|
316
|
+
'.env.example': 'PORT=4000\n',
|
|
317
|
+
'README.md': `# ${pkgName}
|
|
318
|
+
|
|
319
|
+
Starter RRRoutes server scaffold.
|
|
320
|
+
- update the contract import in src/index.ts if needed (${contractImport})
|
|
321
|
+
- run \`npm install\` then \`npm run dev\` to start the API
|
|
322
|
+
`,
|
|
323
|
+
};
|
|
324
|
+
}
|
|
325
|
+
function clientFiles(pkgName, contractImport) {
|
|
326
|
+
return {
|
|
327
|
+
'package.json': clientPackageJson(pkgName),
|
|
328
|
+
'tsconfig.json': baseTsConfig({ lib: ['ES2020', 'DOM'], types: ['node'] }),
|
|
329
|
+
'src/index.ts': clientIndexTs(contractImport),
|
|
330
|
+
'README.md': `# ${pkgName}
|
|
331
|
+
|
|
332
|
+
Starter RRRoutes client scaffold.
|
|
333
|
+
- update the contract import in src/index.ts if needed (${contractImport})
|
|
334
|
+
- the generated QueryClient is exported from src/index.ts
|
|
335
|
+
`,
|
|
336
|
+
};
|
|
337
|
+
}
|
|
338
|
+
function resolveContractImport(variant) {
|
|
339
|
+
if (variant === 'rrr-contract')
|
|
340
|
+
return '';
|
|
341
|
+
return '@your-scope/contract';
|
|
342
|
+
}
|
|
343
|
+
async function scaffoldVariant(variant, targetDir, pkgName) {
|
|
344
|
+
const contractImport = resolveContractImport(variant);
|
|
345
|
+
let files;
|
|
346
|
+
if (variant === 'rrr-contract')
|
|
347
|
+
files = contractFiles(pkgName);
|
|
348
|
+
else if (variant === 'rrr-server')
|
|
349
|
+
files = serverFiles(pkgName, contractImport);
|
|
350
|
+
else
|
|
351
|
+
files = clientFiles(pkgName, contractImport);
|
|
352
|
+
for (const [relative, contents] of Object.entries(files)) {
|
|
353
|
+
// eslint-disable-next-line no-await-in-loop
|
|
354
|
+
await writeFileIfMissing(targetDir, relative, contents);
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
async function promptForVariant() {
|
|
358
|
+
const messageLines = [
|
|
359
|
+
'Pick a package template:',
|
|
360
|
+
VARIANTS.map((opt, idx) => ` [${idx + 1}] ${opt.label}`).join('\n'),
|
|
361
|
+
'Enter 1, 2, or 3: ',
|
|
362
|
+
];
|
|
363
|
+
const message = `${messageLines.join('\n')}`;
|
|
364
|
+
const variant = await promptSingleKey(message, (key) => {
|
|
365
|
+
if (key === '1')
|
|
366
|
+
return VARIANTS[0];
|
|
367
|
+
if (key === '2')
|
|
368
|
+
return VARIANTS[1];
|
|
369
|
+
if (key === '3')
|
|
370
|
+
return VARIANTS[2];
|
|
371
|
+
return undefined;
|
|
372
|
+
});
|
|
373
|
+
return variant;
|
|
374
|
+
}
|
|
375
|
+
async function promptForTargetDir(fallback) {
|
|
376
|
+
const answer = await askLine(`Path for the new package? (${fallback}): `);
|
|
377
|
+
const normalized = answer || fallback;
|
|
378
|
+
return path.resolve(workspaceRoot, normalized);
|
|
379
|
+
}
|
|
380
|
+
export async function createRrrPackage() {
|
|
381
|
+
const variant = await promptForVariant();
|
|
382
|
+
const targetDir = await promptForTargetDir(variant.defaultDir);
|
|
383
|
+
await ensureTargetDir(targetDir);
|
|
384
|
+
const pkgName = derivePackageName(targetDir);
|
|
385
|
+
logGlobal(`Creating ${variant.label} in ${path.relative(workspaceRoot, targetDir) || '.'}`, colors.green);
|
|
386
|
+
await scaffoldVariant(variant.id, targetDir, pkgName);
|
|
387
|
+
logGlobal('Scaffold complete. Install deps and start building!', colors.green);
|
|
388
|
+
}
|
package/dist/manager-cli.js
CHANGED
|
@@ -1,28 +1,46 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { spawnSync } from 'node:child_process'
|
|
3
|
+
import { existsSync } from 'node:fs'
|
|
3
4
|
import path from 'node:path'
|
|
4
|
-
import { fileURLToPath
|
|
5
|
+
import { fileURLToPath } from 'node:url'
|
|
5
6
|
import { createRequire } from 'node:module'
|
|
6
7
|
|
|
7
8
|
const __filename = fileURLToPath(import.meta.url)
|
|
8
9
|
const __dirname = path.dirname(__filename)
|
|
9
10
|
const packageRoot = path.resolve(__dirname, '..')
|
|
10
11
|
const tsconfigPath = path.join(packageRoot, 'tsconfig.base.json')
|
|
11
|
-
const
|
|
12
|
+
const compiledEntry = path.join(packageRoot, 'dist', 'publish.js')
|
|
13
|
+
const sourceEntry = path.join(packageRoot, 'src', 'publish.ts')
|
|
14
|
+
const args = process.argv.slice(2)
|
|
12
15
|
|
|
13
|
-
const
|
|
14
|
-
const
|
|
15
|
-
const registerCode = [
|
|
16
|
-
'import { register } from "node:module";',
|
|
17
|
-
'import { pathToFileURL } from "node:url";',
|
|
18
|
-
`register(${JSON.stringify(tsNodeLoader)}, pathToFileURL(${JSON.stringify(packageRoot + path.sep)}));`,
|
|
19
|
-
].join(' ')
|
|
20
|
-
const registerImport = `data:text/javascript,${encodeURIComponent(registerCode)}`
|
|
16
|
+
const hasCompiledEntry = existsSync(compiledEntry)
|
|
17
|
+
const hasSourceEntry = existsSync(sourceEntry)
|
|
21
18
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
19
|
+
let nodeArgs
|
|
20
|
+
let execOptions
|
|
21
|
+
|
|
22
|
+
if (hasCompiledEntry) {
|
|
23
|
+
nodeArgs = [compiledEntry, ...args]
|
|
24
|
+
execOptions = { env: process.env, stdio: 'inherit' }
|
|
25
|
+
} else if (hasSourceEntry) {
|
|
26
|
+
const require = createRequire(import.meta.url)
|
|
27
|
+
const tsNodeLoader = require.resolve('ts-node/esm.mjs')
|
|
28
|
+
const registerCode = [
|
|
29
|
+
'import { register } from "node:module";',
|
|
30
|
+
'import { pathToFileURL } from "node:url";',
|
|
31
|
+
`register(${JSON.stringify(tsNodeLoader)}, pathToFileURL(${JSON.stringify(packageRoot + path.sep)}));`,
|
|
32
|
+
].join(' ')
|
|
33
|
+
const registerImport = `data:text/javascript,${encodeURIComponent(registerCode)}`
|
|
34
|
+
nodeArgs = ['--import', registerImport, sourceEntry, ...args]
|
|
35
|
+
execOptions = {
|
|
36
|
+
env: { ...process.env, TS_NODE_PROJECT: tsconfigPath },
|
|
37
|
+
stdio: 'inherit',
|
|
38
|
+
}
|
|
39
|
+
} else {
|
|
40
|
+
console.error(
|
|
41
|
+
`manager-cli could not find a publish entry point (checked ${compiledEntry} and ${sourceEntry}).`,
|
|
42
|
+
)
|
|
43
|
+
process.exit(1)
|
|
26
44
|
}
|
|
27
45
|
|
|
28
46
|
const child = spawnSync(process.execPath, nodeArgs, execOptions)
|
package/dist/menu.js
CHANGED
package/dist/packages.js
CHANGED
|
@@ -37,7 +37,7 @@ function deriveSubstitute(name) {
|
|
|
37
37
|
return '';
|
|
38
38
|
const segments = trimmed.split(/[@\/\-]/).filter(Boolean);
|
|
39
39
|
const transformed = segments
|
|
40
|
-
.map((segment) => segment
|
|
40
|
+
.map((segment) => segment)
|
|
41
41
|
.filter(Boolean)
|
|
42
42
|
.join(' ');
|
|
43
43
|
return transformed || trimmed;
|
package/dist/publish.js
CHANGED
|
@@ -5,6 +5,8 @@ import { getOrderedPackages, loadPackages, resolvePackage } from './packages.js'
|
|
|
5
5
|
import { releaseMultiple, releaseSingle, } from './release.js';
|
|
6
6
|
import { ensureWorkingTreeCommitted } from './preflight.js';
|
|
7
7
|
import { publishCliState } from './prompts.js';
|
|
8
|
+
import { createRrrPackage } from './create-package.js';
|
|
9
|
+
import { colors, logGlobal } from './utils/log.js';
|
|
8
10
|
function resolveTargetsFromArg(packages, arg) {
|
|
9
11
|
if (arg.toLowerCase() === 'all')
|
|
10
12
|
return getOrderedPackages(packages);
|
|
@@ -89,14 +91,27 @@ function optsFromParsed(p) {
|
|
|
89
91
|
}
|
|
90
92
|
async function runPackageSelectionLoop(packages, helperArgs) {
|
|
91
93
|
let argv = [...helperArgs];
|
|
94
|
+
let currentPackages = packages;
|
|
92
95
|
// eslint-disable-next-line no-constant-condition
|
|
93
96
|
while (true) {
|
|
94
97
|
let lastStep;
|
|
95
98
|
await runHelperCli({
|
|
96
99
|
title: 'Pick one of the packages or all',
|
|
97
|
-
scripts:
|
|
98
|
-
|
|
99
|
-
|
|
100
|
+
scripts: [
|
|
101
|
+
...buildPackageSelectionMenu(currentPackages, (step) => {
|
|
102
|
+
lastStep = step;
|
|
103
|
+
}),
|
|
104
|
+
{
|
|
105
|
+
name: 'Create package',
|
|
106
|
+
emoji: '✨',
|
|
107
|
+
description: 'Scaffold a new rrr package (contract/server/client)',
|
|
108
|
+
handler: async () => {
|
|
109
|
+
await createRrrPackage();
|
|
110
|
+
currentPackages = await loadPackages();
|
|
111
|
+
lastStep = 'back';
|
|
112
|
+
},
|
|
113
|
+
},
|
|
114
|
+
],
|
|
100
115
|
argv, // pass through CLI args only once; subsequent loops rely on selection
|
|
101
116
|
});
|
|
102
117
|
argv = [];
|
|
@@ -108,10 +123,11 @@ async function main() {
|
|
|
108
123
|
const cliArgs = process.argv.slice(2);
|
|
109
124
|
const parsed = parseCliArgs(cliArgs);
|
|
110
125
|
const packages = await loadPackages();
|
|
111
|
-
if (packages.length === 0)
|
|
112
|
-
throw new Error('No packages found in ./packages');
|
|
113
126
|
// If user provided non-interactive flags, run headless path
|
|
114
127
|
if (parsed.nonInteractive) {
|
|
128
|
+
if (packages.length === 0) {
|
|
129
|
+
throw new Error('No packages found in ./packages');
|
|
130
|
+
}
|
|
115
131
|
publishCliState.autoConfirmAll = true;
|
|
116
132
|
if (!parsed.selectionArg) {
|
|
117
133
|
throw new Error('Non-interactive mode requires a package selection: <pkg> or "all".');
|
|
@@ -128,6 +144,9 @@ async function main() {
|
|
|
128
144
|
await releaseSingle(targets[0], packages, opts);
|
|
129
145
|
return;
|
|
130
146
|
}
|
|
147
|
+
if (packages.length === 0) {
|
|
148
|
+
logGlobal('No packages found in ./packages. Use "Create package" to scaffold one.', colors.yellow);
|
|
149
|
+
}
|
|
131
150
|
// Interactive flow (unchanged): selection menu then step menu
|
|
132
151
|
if (parsed.selectionArg) {
|
|
133
152
|
const targets = resolveTargetsFromArg(packages, parsed.selectionArg);
|