@emeryld/manager 0.6.0 โ 0.6.2
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 +22 -10
- package/dist/create-package/shared.js +7 -0
- package/dist/create-package/variants/client/client_expo_rn.js +4 -0
- package/dist/create-package/variants/client/client_vite_r.js +7 -1
- package/dist/create-package/variants/contract.js +6 -8
- package/dist/create-package/variants/docker.js +21 -142
- package/dist/create-package/variants/empty.js +1 -0
- package/dist/docker.js +128 -0
- package/dist/menu.js +30 -3
- package/dist/packages.js +12 -1
- package/package.json +1 -1
|
@@ -80,16 +80,28 @@ async function promptForContractName() {
|
|
|
80
80
|
}
|
|
81
81
|
async function promptForClientKind(existing) {
|
|
82
82
|
const defaultKind = normalizeClientKind(existing) ?? DEFAULT_CLIENT_KIND;
|
|
83
|
-
|
|
84
|
-
const
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
83
|
+
let selection;
|
|
84
|
+
const scripts = CLIENT_KIND_OPTIONS.map((opt) => ({
|
|
85
|
+
name: opt.label,
|
|
86
|
+
emoji: '๐ป',
|
|
87
|
+
description: opt.summary,
|
|
88
|
+
handler: () => {
|
|
89
|
+
selection = opt.id;
|
|
90
|
+
},
|
|
91
|
+
}));
|
|
92
|
+
// scripts.push({
|
|
93
|
+
// name: `Use default (${defaultKind})`,
|
|
94
|
+
// emoji: 'โ
',
|
|
95
|
+
// handler: () => {
|
|
96
|
+
// selection = defaultKind
|
|
97
|
+
// },
|
|
98
|
+
// })
|
|
99
|
+
await runHelperCli({
|
|
100
|
+
title: 'Select a client template',
|
|
101
|
+
scripts,
|
|
102
|
+
argv: [],
|
|
103
|
+
});
|
|
104
|
+
return selection ?? defaultKind;
|
|
93
105
|
}
|
|
94
106
|
function resolveVariant(key) {
|
|
95
107
|
if (!key)
|
|
@@ -2,6 +2,7 @@ import { readFileSync } from 'node:fs';
|
|
|
2
2
|
import { access, mkdir, writeFile } from 'node:fs/promises';
|
|
3
3
|
import path from 'node:path';
|
|
4
4
|
import { promptYesNoAll } from '../prompts.js';
|
|
5
|
+
import { ModuleResolutionKind } from 'typescript';
|
|
5
6
|
export const workspaceRoot = process.cwd();
|
|
6
7
|
export function isWorkspaceRoot(dir) {
|
|
7
8
|
return path.resolve(dir) === path.resolve(workspaceRoot);
|
|
@@ -347,6 +348,12 @@ export async function packageTsConfig(targetDir, options) {
|
|
|
347
348
|
esModuleInterop: options?.esModuleInterop ?? true,
|
|
348
349
|
allowSyntheticDefaultImports: options?.esModuleInterop ?? true,
|
|
349
350
|
skipLibCheck: options?.skipLibCheck ?? true,
|
|
351
|
+
target: options?.target,
|
|
352
|
+
module: options?.module,
|
|
353
|
+
moduleResolution: options?.ModuleResolutionKind !== undefined
|
|
354
|
+
? ModuleResolutionKind[options.ModuleResolutionKind]
|
|
355
|
+
: undefined,
|
|
356
|
+
declarationMap: options?.declarationMap,
|
|
350
357
|
});
|
|
351
358
|
const config = stripUndefined({
|
|
352
359
|
extends: extendsPath,
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { ModuleResolutionKind } from 'typescript';
|
|
1
2
|
import { BASE_LINT_DEV_DEPENDENCIES, basePackageFiles, basePackageJson, baseScripts, buildReadme, isWorkspaceRoot, packageTsConfig, writeFileIfMissing, } from '../../shared.js';
|
|
2
3
|
const EXPO_CLIENT_SCRIPTS = [
|
|
3
4
|
'dev',
|
|
@@ -480,6 +481,9 @@ export async function scaffoldExpoReactNativeClient(ctx) {
|
|
|
480
481
|
include: ['App.tsx', 'src/**/*.ts', 'src/**/*.tsx'],
|
|
481
482
|
jsx: 'react-native',
|
|
482
483
|
types: ['react', 'react-native'],
|
|
484
|
+
target: 'ES2022',
|
|
485
|
+
module: 'ESNext',
|
|
486
|
+
ModuleResolutionKind: ModuleResolutionKind.Bundler,
|
|
483
487
|
rootDir: '.',
|
|
484
488
|
});
|
|
485
489
|
const files = {
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { ModuleResolutionKind } from 'typescript';
|
|
1
2
|
import { BASE_LINT_DEV_DEPENDENCIES, basePackageFiles, basePackageJson, baseScripts, buildReadme, isWorkspaceRoot, packageTsConfig, writeFileIfMissing, } from '../../shared.js';
|
|
2
3
|
const VITE_CLIENT_SCRIPTS = [
|
|
3
4
|
'dev',
|
|
@@ -525,10 +526,15 @@ function indexHtml() {
|
|
|
525
526
|
export async function scaffoldViteReactClient(ctx) {
|
|
526
527
|
const includePrepare = isWorkspaceRoot(ctx.targetDir);
|
|
527
528
|
const tsconfig = await packageTsConfig(ctx.targetDir, {
|
|
528
|
-
include: ['src
|
|
529
|
+
include: ['src/**/*.ts', 'src/**/*.tsx'],
|
|
529
530
|
lib: ['ES2022', 'DOM'],
|
|
530
531
|
types: ['vite/client'],
|
|
531
532
|
jsx: 'react-jsx',
|
|
533
|
+
target: 'ES2022',
|
|
534
|
+
module: 'ESNext',
|
|
535
|
+
ModuleResolutionKind: ModuleResolutionKind.Bundler,
|
|
536
|
+
esModuleInterop: true,
|
|
537
|
+
skipLibCheck: true,
|
|
532
538
|
});
|
|
533
539
|
const files = {
|
|
534
540
|
'package.json': vitePackageJson(ctx.pkgName, ctx.contractName, includePrepare),
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { ModuleResolutionKind } from 'typescript';
|
|
1
2
|
import { BASE_LINT_DEV_DEPENDENCIES, basePackageFiles, basePackageJson, buildReadme, baseScripts, packageTsConfig, isWorkspaceRoot, writeFileIfMissing, } from '../shared.js';
|
|
2
3
|
const CONTRACT_SCRIPTS = [
|
|
3
4
|
'dev',
|
|
@@ -16,16 +17,9 @@ const CONTRACT_SCRIPTS = [
|
|
|
16
17
|
* in some module shapes (CJS export= or default export). To make scaffolds compile reliably,
|
|
17
18
|
* we import as a namespace and then grab from either `default` or the namespace object.
|
|
18
19
|
*/
|
|
19
|
-
export const CONTRACT_TS = `import
|
|
20
|
+
export const CONTRACT_TS = `import { defineSocketEvents, finalize, resource } from '@emeryld/rrroutes-contract'
|
|
20
21
|
import { z } from 'zod'
|
|
21
22
|
|
|
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
|
-
|
|
29
23
|
const routes = resource('/api')
|
|
30
24
|
.sub(
|
|
31
25
|
resource('health')
|
|
@@ -121,6 +115,10 @@ async function contractFiles(pkgName, targetDir) {
|
|
|
121
115
|
const tsconfig = await packageTsConfig(targetDir, {
|
|
122
116
|
include: ['src/**/*.ts', 'src/**/*.tsx'],
|
|
123
117
|
skipLibCheck: true,
|
|
118
|
+
"target": "ES2022",
|
|
119
|
+
"module": "ESNext",
|
|
120
|
+
"declarationMap": true,
|
|
121
|
+
ModuleResolutionKind: ModuleResolutionKind.Bundler,
|
|
124
122
|
});
|
|
125
123
|
return {
|
|
126
124
|
'package.json': contractPackageJson(pkgName, { includePrepare }),
|
|
@@ -18,19 +18,29 @@ const DOCKER_SCRIPTS = [
|
|
|
18
18
|
'docker:clean',
|
|
19
19
|
'docker:reset',
|
|
20
20
|
];
|
|
21
|
+
function sanitizeContainerName(name) {
|
|
22
|
+
const normalized = name
|
|
23
|
+
.toLowerCase()
|
|
24
|
+
.replace(/[^a-z0-9]/gi, '-')
|
|
25
|
+
.replace(/-+/g, '-')
|
|
26
|
+
.replace(/^-+|-+$/g, '') || 'rrr-service';
|
|
27
|
+
return normalized;
|
|
28
|
+
}
|
|
21
29
|
function dockerPackageJson(name, options) {
|
|
30
|
+
const image = `${name}:latest`;
|
|
31
|
+
const container = sanitizeContainerName(name);
|
|
32
|
+
const portVar = '${PORT:-3000}';
|
|
22
33
|
return basePackageJson({
|
|
23
34
|
name,
|
|
24
35
|
scripts: baseScripts('tsx watch src/index.ts', {
|
|
25
36
|
start: 'node dist/index.js',
|
|
26
|
-
'docker:
|
|
27
|
-
'docker:
|
|
28
|
-
'docker:
|
|
29
|
-
'docker:
|
|
30
|
-
'docker:
|
|
31
|
-
'docker:
|
|
32
|
-
'docker:
|
|
33
|
-
'docker:reset': 'npm run docker:cli -- reset',
|
|
37
|
+
'docker:build': `docker build -t ${image} .`,
|
|
38
|
+
'docker:up': `npm run docker:build && PORT=${portVar} docker run -d --rm -p ${portVar}:${portVar} --name ${container} ${image}`,
|
|
39
|
+
'docker:dev': `npm run docker:build && PORT=${portVar} docker run -d --rm -p ${portVar}:${portVar} --name ${container} ${image} && docker logs -f ${container}`,
|
|
40
|
+
'docker:logs': `docker logs -f ${container}`,
|
|
41
|
+
'docker:stop': `docker stop ${container}`,
|
|
42
|
+
'docker:clean': `docker stop ${container} || true; docker rm -f ${container} || true`,
|
|
43
|
+
'docker:reset': `npm run docker:clean && docker rmi -f ${image} || true`,
|
|
34
44
|
}, { includePrepare: options?.includePrepare }),
|
|
35
45
|
dependencies: {
|
|
36
46
|
cors: '^2.8.5',
|
|
@@ -41,7 +51,6 @@ function dockerPackageJson(name, options) {
|
|
|
41
51
|
'@types/cors': '^2.8.5',
|
|
42
52
|
'@types/express': '^5.0.6',
|
|
43
53
|
'@types/node': '^24.10.2',
|
|
44
|
-
'docker-cli-js': '^2.10.0',
|
|
45
54
|
},
|
|
46
55
|
});
|
|
47
56
|
}
|
|
@@ -105,7 +114,6 @@ async function dockerFiles(pkgName, targetDir) {
|
|
|
105
114
|
'package.json': dockerPackageJson(pkgName, { includePrepare }),
|
|
106
115
|
'tsconfig.json': tsconfig,
|
|
107
116
|
'src/index.ts': dockerIndexTs(),
|
|
108
|
-
'scripts/docker.ts': dockerCliScript(pkgName),
|
|
109
117
|
'.dockerignore': DOCKER_DOCKERIGNORE,
|
|
110
118
|
...basePackageFiles(),
|
|
111
119
|
Dockerfile: dockerDockerfile(),
|
|
@@ -143,145 +151,16 @@ async function dockerFiles(pkgName, targetDir) {
|
|
|
143
151
|
}),
|
|
144
152
|
};
|
|
145
153
|
}
|
|
146
|
-
function dockerCliScript(pkgName) {
|
|
147
|
-
return `#!/usr/bin/env tsx
|
|
148
|
-
import { readFile } from 'node:fs/promises'
|
|
149
|
-
import path from 'node:path'
|
|
150
|
-
import { fileURLToPath } from 'node:url'
|
|
151
|
-
import { Docker } from 'docker-cli-js'
|
|
152
|
-
import { runHelperCli } from '@emeryld/manager/dist/helper-cli.js'
|
|
153
|
-
|
|
154
|
-
const __filename = fileURLToPath(import.meta.url)
|
|
155
|
-
const __dirname = path.dirname(__filename)
|
|
156
|
-
const pkgRoot = path.join(__dirname, '..')
|
|
157
|
-
const pkgRaw = await readFile(path.join(pkgRoot, 'package.json'), 'utf8')
|
|
158
|
-
const pkg = JSON.parse(pkgRaw) as { name?: string }
|
|
159
|
-
const image = \`\${pkg.name ?? '${pkgName}'}:latest\`
|
|
160
|
-
const container =
|
|
161
|
-
(pkg.name ?? '${pkgName}')
|
|
162
|
-
.replace(/[^a-z0-9]/gi, '-')
|
|
163
|
-
.replace(/^-+|-+$/g, '') || 'rrr-service'
|
|
164
|
-
const port = process.env.PORT ?? '3000'
|
|
165
|
-
const docker = new Docker({ spawnOptions: { stdio: 'inherit' } })
|
|
166
|
-
|
|
167
|
-
async function safe(run: () => Promise<unknown>) {
|
|
168
|
-
try {
|
|
169
|
-
await run()
|
|
170
|
-
} catch (error: unknown) {
|
|
171
|
-
console.warn(error instanceof Error ? error.message : String(error))
|
|
172
|
-
}
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
function printHelp() {
|
|
176
|
-
console.log(
|
|
177
|
-
[
|
|
178
|
-
'Docker helper commands:',
|
|
179
|
-
' build -> docker build -t ${pkgName}:latest .',
|
|
180
|
-
' up -> build then run in detached mode',
|
|
181
|
-
' dev -> build, run, and tail logs',
|
|
182
|
-
' run -> run existing image detached',
|
|
183
|
-
' logs -> docker logs -f <container>',
|
|
184
|
-
' stop -> docker stop <container>',
|
|
185
|
-
' clean -> docker stop/rm <container>',
|
|
186
|
-
' reset -> clean container and remove image',
|
|
187
|
-
].join('\\n'),
|
|
188
|
-
)
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
await runHelperCli({
|
|
192
|
-
title: 'Docker helper',
|
|
193
|
-
argv: process.argv.slice(2),
|
|
194
|
-
scripts: [
|
|
195
|
-
{
|
|
196
|
-
name: 'build',
|
|
197
|
-
emoji: '๐จ',
|
|
198
|
-
description: 'docker build -t image .',
|
|
199
|
-
handler: () => docker.command(\`build -t \${image} .\`),
|
|
200
|
-
},
|
|
201
|
-
{
|
|
202
|
-
name: 'up',
|
|
203
|
-
emoji: 'โฌ๏ธ',
|
|
204
|
-
description: 'Build + run detached',
|
|
205
|
-
handler: async () => {
|
|
206
|
-
await docker.command(\`build -t \${image} .\`)
|
|
207
|
-
await docker.command(\`run -d --rm -p \${port}:\${port} --name \${container} \${image}\`)
|
|
208
|
-
},
|
|
209
|
-
},
|
|
210
|
-
{
|
|
211
|
-
name: 'dev',
|
|
212
|
-
emoji: '๐ ๏ธ',
|
|
213
|
-
description: 'Build, run, tail logs',
|
|
214
|
-
handler: async () => {
|
|
215
|
-
await docker.command(\`build -t \${image} .\`)
|
|
216
|
-
await docker.command(\`run -d --rm -p \${port}:\${port} --name \${container} \${image}\`)
|
|
217
|
-
await docker.command(\`logs -f \${container}\`)
|
|
218
|
-
},
|
|
219
|
-
},
|
|
220
|
-
{
|
|
221
|
-
name: 'run',
|
|
222
|
-
emoji: '๐',
|
|
223
|
-
description: 'Run existing image detached',
|
|
224
|
-
handler: () =>
|
|
225
|
-
docker.command(\`run -d --rm -p \${port}:\${port} --name \${container} \${image}\`),
|
|
226
|
-
},
|
|
227
|
-
{
|
|
228
|
-
name: 'logs',
|
|
229
|
-
emoji: '๐',
|
|
230
|
-
description: 'Tail docker logs',
|
|
231
|
-
handler: () => docker.command(\`logs -f \${container}\`),
|
|
232
|
-
},
|
|
233
|
-
{
|
|
234
|
-
name: 'stop',
|
|
235
|
-
emoji: '๐',
|
|
236
|
-
description: 'Stop container',
|
|
237
|
-
handler: () => docker.command(\`stop \${container}\`),
|
|
238
|
-
},
|
|
239
|
-
{
|
|
240
|
-
name: 'clean',
|
|
241
|
-
emoji: '๐งน',
|
|
242
|
-
description: 'Stop + remove container',
|
|
243
|
-
handler: async () => {
|
|
244
|
-
await safe(() => docker.command(\`stop \${container}\`))
|
|
245
|
-
await safe(() => docker.command(\`rm -f \${container}\`))
|
|
246
|
-
},
|
|
247
|
-
},
|
|
248
|
-
{
|
|
249
|
-
name: 'reset',
|
|
250
|
-
emoji: 'โป๏ธ',
|
|
251
|
-
description: 'Clean container and remove image',
|
|
252
|
-
handler: async () => {
|
|
253
|
-
await safe(() => docker.command(\`stop \${container}\`))
|
|
254
|
-
await safe(() => docker.command(\`rm -f \${container}\`))
|
|
255
|
-
await safe(() => docker.command(\`rmi -f \${image}\`))
|
|
256
|
-
},
|
|
257
|
-
},
|
|
258
|
-
{
|
|
259
|
-
name: 'help',
|
|
260
|
-
emoji: 'โน๏ธ',
|
|
261
|
-
description: 'Print commands',
|
|
262
|
-
handler: () => {
|
|
263
|
-
printHelp()
|
|
264
|
-
},
|
|
265
|
-
},
|
|
266
|
-
],
|
|
267
|
-
})
|
|
268
|
-
`;
|
|
269
|
-
}
|
|
270
154
|
export const dockerVariant = {
|
|
271
155
|
id: 'rrr-docker',
|
|
272
156
|
label: 'dockerized service',
|
|
273
157
|
defaultDir: 'packages/rrr-docker',
|
|
274
|
-
summary: 'Express service plus Dockerfile and
|
|
275
|
-
keyFiles: [
|
|
276
|
-
'src/index.ts',
|
|
277
|
-
'scripts/docker.ts',
|
|
278
|
-
'Dockerfile',
|
|
279
|
-
'README.md',
|
|
280
|
-
],
|
|
158
|
+
summary: 'Express service plus Dockerfile and docker scripts for local runs.',
|
|
159
|
+
keyFiles: ['src/index.ts', 'Dockerfile', 'README.md'],
|
|
281
160
|
scripts: DOCKER_SCRIPTS,
|
|
282
161
|
notes: [
|
|
283
162
|
'Use docker:dev or docker:up to build and run the container quickly.',
|
|
284
|
-
'
|
|
163
|
+
'Manager CLI surfaces Docker helpers automatically when a Dockerfile is present.',
|
|
285
164
|
],
|
|
286
165
|
async scaffold(ctx) {
|
|
287
166
|
const files = await dockerFiles(ctx.pkgName, ctx.targetDir);
|
package/dist/docker.js
ADDED
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import { access } from 'node:fs/promises';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { runHelperCli } from './helper-cli.js';
|
|
4
|
+
import { run } from './utils/run.js';
|
|
5
|
+
function sanitizeName(name) {
|
|
6
|
+
return name
|
|
7
|
+
.toLowerCase()
|
|
8
|
+
.replace(/[^a-z0-9]/gi, '-')
|
|
9
|
+
.replace(/-+/g, '-')
|
|
10
|
+
.replace(/^-+|-+$/g, '');
|
|
11
|
+
}
|
|
12
|
+
async function ensureDockerfileExists(dockerfilePath) {
|
|
13
|
+
try {
|
|
14
|
+
await access(dockerfilePath);
|
|
15
|
+
}
|
|
16
|
+
catch {
|
|
17
|
+
throw new Error(`Dockerfile not found at ${dockerfilePath}`);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
async function dockerCommand(args, pkg, options) {
|
|
21
|
+
try {
|
|
22
|
+
await run('docker', args, { cwd: pkg.path });
|
|
23
|
+
}
|
|
24
|
+
catch (error) {
|
|
25
|
+
if (!options?.ignoreErrors)
|
|
26
|
+
throw error;
|
|
27
|
+
// eslint-disable-next-line no-console
|
|
28
|
+
console.warn(error instanceof Error ? error.message : String(error));
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
function printHelp(image, container) {
|
|
32
|
+
console.log([
|
|
33
|
+
'Docker helper commands:',
|
|
34
|
+
` build -> docker build -t ${image} .`,
|
|
35
|
+
' up -> build then run in detached mode',
|
|
36
|
+
' dev -> build, run, and tail logs',
|
|
37
|
+
' run -> run existing image detached',
|
|
38
|
+
` logs -> docker logs -f ${container}`,
|
|
39
|
+
` stop -> docker stop ${container}`,
|
|
40
|
+
` clean -> docker stop/rm ${container}`,
|
|
41
|
+
` reset -> clean container and remove ${image}`,
|
|
42
|
+
].join('\n'));
|
|
43
|
+
}
|
|
44
|
+
export async function openDockerHelper(pkg) {
|
|
45
|
+
const dockerfilePath = pkg.dockerfilePath ?? path.join(pkg.path, 'Dockerfile');
|
|
46
|
+
await ensureDockerfileExists(dockerfilePath);
|
|
47
|
+
const image = `${pkg.name ?? pkg.dirName}:latest`;
|
|
48
|
+
const container = sanitizeName(pkg.name ?? pkg.dirName) || 'rrr-service';
|
|
49
|
+
const port = process.env.PORT ?? '3000';
|
|
50
|
+
const dockerfileLabel = path
|
|
51
|
+
.relative(process.cwd(), dockerfilePath)
|
|
52
|
+
.replace(/\\/g, '/');
|
|
53
|
+
const scripts = [
|
|
54
|
+
{
|
|
55
|
+
name: 'build',
|
|
56
|
+
emoji: '๐จ',
|
|
57
|
+
description: 'docker build -t image .',
|
|
58
|
+
handler: () => dockerCommand(['build', '-t', image, '.'], pkg),
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
name: 'up',
|
|
62
|
+
emoji: 'โฌ๏ธ',
|
|
63
|
+
description: 'Build + run detached',
|
|
64
|
+
handler: async () => {
|
|
65
|
+
await dockerCommand(['build', '-t', image, '.'], pkg);
|
|
66
|
+
await dockerCommand(['run', '-d', '--rm', '-p', `${port}:${port}`, '--name', container, image], pkg);
|
|
67
|
+
},
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
name: 'dev',
|
|
71
|
+
emoji: '๐ ๏ธ',
|
|
72
|
+
description: 'Build, run, tail logs',
|
|
73
|
+
handler: async () => {
|
|
74
|
+
await dockerCommand(['build', '-t', image, '.'], pkg);
|
|
75
|
+
await dockerCommand(['run', '-d', '--rm', '-p', `${port}:${port}`, '--name', container, image], pkg);
|
|
76
|
+
await dockerCommand(['logs', '-f', container], pkg);
|
|
77
|
+
},
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
name: 'run',
|
|
81
|
+
emoji: '๐',
|
|
82
|
+
description: 'Run existing image detached',
|
|
83
|
+
handler: () => dockerCommand(['run', '-d', '--rm', '-p', `${port}:${port}`, '--name', container, image], pkg),
|
|
84
|
+
},
|
|
85
|
+
{
|
|
86
|
+
name: 'logs',
|
|
87
|
+
emoji: '๐',
|
|
88
|
+
description: 'Tail docker logs',
|
|
89
|
+
handler: () => dockerCommand(['logs', '-f', container], pkg),
|
|
90
|
+
},
|
|
91
|
+
{
|
|
92
|
+
name: 'stop',
|
|
93
|
+
emoji: '๐',
|
|
94
|
+
description: 'Stop container',
|
|
95
|
+
handler: () => dockerCommand(['stop', container], pkg),
|
|
96
|
+
},
|
|
97
|
+
{
|
|
98
|
+
name: 'clean',
|
|
99
|
+
emoji: '๐งน',
|
|
100
|
+
description: 'Stop + remove container',
|
|
101
|
+
handler: async () => {
|
|
102
|
+
await dockerCommand(['stop', container], pkg, { ignoreErrors: true });
|
|
103
|
+
await dockerCommand(['rm', '-f', container], pkg, { ignoreErrors: true });
|
|
104
|
+
},
|
|
105
|
+
},
|
|
106
|
+
{
|
|
107
|
+
name: 'reset',
|
|
108
|
+
emoji: 'โป๏ธ',
|
|
109
|
+
description: 'Clean container and remove image',
|
|
110
|
+
handler: async () => {
|
|
111
|
+
await dockerCommand(['stop', container], pkg, { ignoreErrors: true });
|
|
112
|
+
await dockerCommand(['rm', '-f', container], pkg, { ignoreErrors: true });
|
|
113
|
+
await dockerCommand(['rmi', '-f', image], pkg, { ignoreErrors: true });
|
|
114
|
+
},
|
|
115
|
+
},
|
|
116
|
+
{
|
|
117
|
+
name: 'help',
|
|
118
|
+
emoji: 'โน๏ธ',
|
|
119
|
+
description: 'Print commands',
|
|
120
|
+
handler: () => printHelp(image, container),
|
|
121
|
+
},
|
|
122
|
+
];
|
|
123
|
+
await runHelperCli({
|
|
124
|
+
title: `Docker helper for ${pkg.name ?? pkg.dirName} (${dockerfileLabel})`,
|
|
125
|
+
scripts,
|
|
126
|
+
argv: [],
|
|
127
|
+
});
|
|
128
|
+
}
|
package/dist/menu.js
CHANGED
|
@@ -1,9 +1,12 @@
|
|
|
1
|
+
// src/menu.js
|
|
2
|
+
import path from 'node:path';
|
|
1
3
|
import { colors, globalEmoji } from './utils/log.js';
|
|
2
4
|
import { updateDependencies, testAll, testSingle, buildAll, buildSingle, } from './workspace.js';
|
|
3
5
|
import { releaseMultiple, releaseSingle } from './release.js';
|
|
4
6
|
import { getOrderedPackages } from './packages.js';
|
|
5
7
|
import { runHelperCli } from './helper-cli.js';
|
|
6
8
|
import { ensureWorkingTreeCommitted } from './preflight.js';
|
|
9
|
+
import { openDockerHelper } from './docker.js';
|
|
7
10
|
import { run } from './utils/run.js';
|
|
8
11
|
function makeManagerStepEntries(targets, packages, state, options) {
|
|
9
12
|
const includeBack = options?.includeBack ?? true;
|
|
@@ -93,6 +96,19 @@ function makeManagerStepEntries(targets, packages, state, options) {
|
|
|
93
96
|
function makePackageScriptEntries(pkg) {
|
|
94
97
|
const scripts = pkg.json?.scripts ?? {};
|
|
95
98
|
const entries = [];
|
|
99
|
+
if (pkg.dockerfilePath) {
|
|
100
|
+
const dockerLabel = path
|
|
101
|
+
.relative(process.cwd(), pkg.dockerfilePath)
|
|
102
|
+
.replace(/\\/g, '/');
|
|
103
|
+
entries.push({
|
|
104
|
+
name: `Dockerfile (${dockerLabel})`,
|
|
105
|
+
emoji: '๐ณ',
|
|
106
|
+
description: 'manager -> opens the docker cli',
|
|
107
|
+
handler: async () => {
|
|
108
|
+
await openDockerHelper(pkg);
|
|
109
|
+
},
|
|
110
|
+
});
|
|
111
|
+
}
|
|
96
112
|
Object.entries(scripts)
|
|
97
113
|
.sort(([a], [b]) => a.localeCompare(b))
|
|
98
114
|
.forEach(([name, command]) => {
|
|
@@ -147,9 +163,20 @@ export async function runStepLoop(targets, packages) {
|
|
|
147
163
|
emoji: globalEmoji,
|
|
148
164
|
description: 'update/test/build/publish',
|
|
149
165
|
handler: async () => {
|
|
150
|
-
const managerEntries =
|
|
151
|
-
|
|
152
|
-
|
|
166
|
+
const managerEntries = [
|
|
167
|
+
...makeManagerStepEntries(targets, packages, state, {
|
|
168
|
+
includeBack: false,
|
|
169
|
+
}),
|
|
170
|
+
{
|
|
171
|
+
name: 'back',
|
|
172
|
+
emoji: 'โฉ๏ธ',
|
|
173
|
+
description: 'Return to package scripts',
|
|
174
|
+
handler: () => {
|
|
175
|
+
// no state change; just exit to the package scripts menu
|
|
176
|
+
state.lastStep = undefined;
|
|
177
|
+
},
|
|
178
|
+
},
|
|
179
|
+
];
|
|
153
180
|
await runHelperCli({
|
|
154
181
|
title: `Manager actions for ${pkg.name}`,
|
|
155
182
|
scripts: managerEntries,
|
package/dist/packages.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// src/packages.js
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import { pathToFileURL } from 'node:url';
|
|
4
|
-
import { readdir, readFile } from 'node:fs/promises';
|
|
4
|
+
import { readdir, readFile, stat } from 'node:fs/promises';
|
|
5
5
|
const rootDir = process.cwd();
|
|
6
6
|
const ignoredDirs = new Set([
|
|
7
7
|
'node_modules',
|
|
@@ -181,6 +181,16 @@ export async function loadPackages() {
|
|
|
181
181
|
byName.get((pkgName ?? '').toLowerCase());
|
|
182
182
|
const substitute = meta?.substitute ?? deriveSubstitute(pkgName) ?? path.basename(pkgDir);
|
|
183
183
|
const color = meta?.color ?? colorFromSeed(pkgName);
|
|
184
|
+
let dockerfilePath;
|
|
185
|
+
try {
|
|
186
|
+
const candidate = path.join(pkgDir, 'Dockerfile');
|
|
187
|
+
const stats = await stat(candidate);
|
|
188
|
+
if (stats.isFile())
|
|
189
|
+
dockerfilePath = candidate;
|
|
190
|
+
}
|
|
191
|
+
catch {
|
|
192
|
+
// ignore missing Dockerfile
|
|
193
|
+
}
|
|
184
194
|
packages.push({
|
|
185
195
|
dirName: path.basename(pkgDir),
|
|
186
196
|
relativeDir: relativePath,
|
|
@@ -191,6 +201,7 @@ export async function loadPackages() {
|
|
|
191
201
|
name: pkgName ?? path.basename(pkgDir),
|
|
192
202
|
substitute,
|
|
193
203
|
color,
|
|
204
|
+
dockerfilePath,
|
|
194
205
|
});
|
|
195
206
|
}
|
|
196
207
|
catch (error) {
|