create-loutre 0.1.0 → 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/README.md ADDED
@@ -0,0 +1,65 @@
1
+ # create-loutre
2
+
3
+ Loutre Applicationをstarterから作成するproject initializerです。Runtime targetとpackage managerを選び、開発・test・buildまで始められるprojectを生成します。
4
+
5
+ ## Quick Start
6
+
7
+ ```sh
8
+ npm create loutre@latest my-app
9
+ ```
10
+
11
+ BunやDenoからも実行できます。
12
+
13
+ ```sh
14
+ bun create loutre my-app
15
+ deno x -A npm:create-loutre@latest my-app
16
+ ```
17
+
18
+ 対話形式でtargetとpackage managerを選択できます。
19
+
20
+ ## Targets
21
+
22
+ - Node.js
23
+ - Bun
24
+ - Deno
25
+ - Cloudflare Workers
26
+ - AWS Lambda
27
+
28
+ ## Package managers
29
+
30
+ - npm
31
+ - pnpm
32
+ - Yarn
33
+ - Bun
34
+ - Deno
35
+
36
+ ## Non-interactive usage
37
+
38
+ Targetとpackage managerはoptionで指定できます。
39
+
40
+ ```sh
41
+ npm create loutre@latest my-app -- --target cloudflare-workers --package-manager pnpm
42
+ ```
43
+
44
+ 主なoption:
45
+
46
+ | Option | Description |
47
+ | ------------------------------------- | ----------------------------- |
48
+ | `--target <target>` | Runtime targetを指定 |
49
+ | `--package-manager <package-manager>` | package managerを指定 |
50
+ | `--yes` | 未指定項目にdefaultを使用 |
51
+ | `--no-install` | dependency installationをskip |
52
+
53
+ ## Generated project
54
+
55
+ starterにはTypeScript configurationに加えて、Vitest、Oxlint、Oxfmtとsample testが含まれます。生成したprojectでは`verify` scriptからformat、lint、type / Application Graph check、test、target固有buildをまとめて実行できます。
56
+
57
+ ## Documentation
58
+
59
+ - [Getting Started](https://github.com/come25136/loutrejs/blob/main/docs/getting-started.md)
60
+ - [Examples](https://github.com/come25136/loutrejs/tree/main/examples)
61
+ - [Loutre](https://github.com/come25136/loutrejs)
62
+
63
+ ## License
64
+
65
+ [MIT](https://github.com/come25136/loutrejs/blob/main/LICENSE)
package/dist/app.js CHANGED
@@ -31,7 +31,7 @@ async function assertTargetIsEmpty(targetDirectory) {
31
31
  try {
32
32
  const entries = await readdir(targetDirectory);
33
33
  if (entries.length > 0) {
34
- throw new Error(`生成先が空ではありません: ${targetDirectory}`);
34
+ throw new Error(`Target directory is not empty: ${targetDirectory}`);
35
35
  }
36
36
  }
37
37
  catch (error) {
@@ -56,7 +56,7 @@ function packageNameFor(targetDirectory) {
56
56
  name.length > 214 ||
57
57
  name.startsWith('.') ||
58
58
  name.startsWith('_')) {
59
- throw new Error(`npm package名を生成できません: ${targetDirectory}`);
59
+ throw new Error(`Could not derive an npm package name from: ${targetDirectory}`);
60
60
  }
61
61
  return name;
62
62
  }
package/dist/cli.d.ts CHANGED
@@ -1,3 +1,4 @@
1
+ import { type PresentationRenderOptions } from '@loutrejs/loutre/presentation';
1
2
  import { type PackageManager } from './options.js';
2
3
  interface SelectOption {
3
4
  readonly value: string;
@@ -6,6 +7,7 @@ interface SelectOption {
6
7
  export interface CreateLoutreCliIO {
7
8
  readonly cwd: string;
8
9
  readonly detectedPackageManager?: PackageManager;
10
+ readonly terminal?: PresentationRenderOptions;
9
11
  readonly prompt?: (message: string, initialValue: string) => Promise<string | undefined>;
10
12
  readonly select?: (message: string, options: readonly SelectOption[], initialValue: string) => Promise<string | undefined>;
11
13
  readonly install: (directory: string, packageManager: PackageManager) => Promise<number>;
package/dist/cli.js CHANGED
@@ -3,6 +3,7 @@ import { relative } from 'node:path';
3
3
  import { isCancel, select, text } from '@clack/prompts';
4
4
  import { Command, CommanderError, Option } from 'commander';
5
5
  import { bootstrap } from '@loutrejs/loutre/host';
6
+ import { detectPresentationTerminal, renderLoutreBrand, } from '@loutrejs/loutre/presentation';
6
7
  import application, { createProject } from './app.js';
7
8
  import { installCommand, packageManagerLabels, packageManagers, projectTargets, runScriptCommand, targetLabels, } from './options.js';
8
9
  export async function runCreateLoutre(args, io = createProcessIO()) {
@@ -16,6 +17,7 @@ export async function runCreateLoutre(args, io = createProcessIO()) {
16
17
  io.stdout(helpText());
17
18
  return 0;
18
19
  }
20
+ io.stdout(renderLoutreBrand(io.terminal ?? { isTTY: false, color: false }));
19
21
  const directory = await resolveDirectory(parsed.directory, parsed.yes, io);
20
22
  if (!directory)
21
23
  return 2;
@@ -33,20 +35,20 @@ export async function runCreateLoutre(args, io = createProcessIO()) {
33
35
  packageManager,
34
36
  target,
35
37
  });
36
- io.stdout(`Loutre Applicationを作成しました: ${result.targetDirectory}`);
38
+ io.stdout(`Created Loutre Application: ${result.targetDirectory}`);
37
39
  io.stdout(`Target: ${targetLabels[target]}`);
38
40
  io.stdout(`Package manager: ${packageManagerLabels[packageManager]}`);
39
41
  if (parsed.install) {
40
- io.stdout('依存関係をインストールします。');
42
+ io.stdout('Installing dependencies...');
41
43
  const installCode = await io.install(result.targetDirectory, packageManager);
42
44
  if (installCode !== 0) {
43
- io.stderr(`${installCommand(packageManager)}に失敗しました。生成したファイルは残しています。`);
45
+ io.stderr(`${installCommand(packageManager)} failed. Generated files have been kept.`);
44
46
  return installCode;
45
47
  }
46
48
  }
47
49
  const nextDirectory = relative(io.cwd, result.targetDirectory);
48
50
  io.stdout('');
49
- io.stdout('次のコマンド:');
51
+ io.stdout('Next steps:');
50
52
  if (nextDirectory && nextDirectory !== '.') {
51
53
  io.stdout(` cd ${quoteShellArgument(nextDirectory)}`);
52
54
  }
@@ -77,9 +79,9 @@ function parseArgs(args) {
77
79
  .addOption(new Option('--package-manager <package-manager>').choices([
78
80
  ...packageManagers,
79
81
  ]))
80
- .option('-y, --yes', '未指定の選択肢に既定値を使用する')
81
- .option('--no-install', '依存関係をinstallしない')
82
- .option('-h, --help', 'helpを表示する')
82
+ .option('-y, --yes', 'Use defaults for unspecified options')
83
+ .option('--no-install', 'Skip dependency installation')
84
+ .option('-h, --help', 'Show help')
83
85
  .allowExcessArguments(false);
84
86
  try {
85
87
  command.parse(args.filter((value) => value !== '--'), { from: 'user' });
@@ -108,7 +110,7 @@ async function resolveDirectory(requested, yes, io) {
108
110
  if (yes)
109
111
  return 'loutre-app';
110
112
  if (!io.prompt) {
111
- io.stderr('生成先を指定してください。例: npm create loutre@latest my-app');
113
+ io.stderr('Specify a target directory. Example: npm create loutre@latest my-app');
112
114
  return undefined;
113
115
  }
114
116
  const answer = await io.prompt('Project name', 'loutre-app');
@@ -141,6 +143,7 @@ function createProcessIO() {
141
143
  return {
142
144
  cwd: process.cwd(),
143
145
  detectedPackageManager: detectPackageManager(process.env.npm_config_user_agent, globalThis),
146
+ terminal: detectPresentationTerminal(process.stdout, process.env),
144
147
  ...(interactive ? { prompt: terminalPrompt, select: terminalSelect } : {}),
145
148
  install: installDependencies,
146
149
  stdout: (value) => process.stdout.write(`${value}\n`),
@@ -201,8 +204,8 @@ function helpText() {
201
204
  'Options:',
202
205
  ' --target <target> node | bun | deno | cloudflare-workers | aws-lambda',
203
206
  ' --package-manager <package-manager> npm | pnpm | yarn | bun | deno',
204
- ' -y, --yes 未指定の選択肢に既定値を使用する',
205
- ' --no-install 依存関係をinstallしない',
206
- ' -h, --help helpを表示する',
207
+ ' -y, --yes Use defaults for unspecified options',
208
+ ' --no-install Skip dependency installation',
209
+ ' -h, --help Show help',
207
210
  ].join('\n');
208
211
  }
package/dist/starter.js CHANGED
@@ -8,7 +8,7 @@ const loutreVersion = requiredDependencyVersion('@loutrejs/loutre');
8
8
  function requiredDependencyVersion(name) {
9
9
  const version = packageManifest.dependencies[name];
10
10
  if (!version)
11
- throw new Error(`${name}のversionがpackage.jsonにありません。`);
11
+ throw new Error(`${name} version is missing from package.json.`);
12
12
  return version;
13
13
  }
14
14
  const baseTemplateDirectory = fileURLToPath(new URL('../templates/base/', import.meta.url));
@@ -136,9 +136,9 @@ function renderPackageJson(options) {
136
136
  function developmentSection(options) {
137
137
  if (options.target === 'aws-lambda') {
138
138
  return [
139
- '## 開発',
139
+ '## Development',
140
140
  '',
141
- 'Lambda targetはlocal HTTP listenerを起動しません。Applicationの挙動はtestで確認します。',
141
+ 'The Lambda target does not start a local HTTP listener. Verify Application behavior with tests.',
142
142
  '',
143
143
  '```sh',
144
144
  runScriptCommand(options.packageManager, 'test:watch'),
@@ -146,15 +146,15 @@ function developmentSection(options) {
146
146
  ].join('\n');
147
147
  }
148
148
  return [
149
- '## 開発',
149
+ '## Development',
150
150
  '',
151
151
  '```sh',
152
152
  runScriptCommand(options.packageManager, 'dev'),
153
153
  '```',
154
154
  '',
155
155
  options.target === 'cloudflare-workers'
156
- ? 'WranglerがlocalのCloudflare Workers環境を起動します。'
157
- : '<http://127.0.0.1:3000> へアクセスするとJSONレスポンスを返します。',
156
+ ? 'Wrangler starts a local Cloudflare Workers environment.'
157
+ : 'Open <http://127.0.0.1:3000> to receive a JSON response.',
158
158
  ].join('\n');
159
159
  }
160
160
  function deploymentSection(options) {
@@ -175,7 +175,7 @@ function deploymentSection(options) {
175
175
  runScriptCommand(options.packageManager, 'build'),
176
176
  '```',
177
177
  '',
178
- '`dist/index.mjs`の`handler`をAWS Lambdaのhandlerとしてdeployします。',
178
+ 'Deploy the `handler` export from `dist/index.mjs` as the AWS Lambda handler.',
179
179
  ].join('\n');
180
180
  }
181
181
  return [
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-loutre",
3
- "version": "0.1.0",
3
+ "version": "0.3.0",
4
4
  "description": "Loutre project initializer",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -18,7 +18,7 @@
18
18
  },
19
19
  "dependencies": {
20
20
  "@clack/prompts": "^1.7.0",
21
- "@loutrejs/loutre": "^0.1.0",
21
+ "@loutrejs/loutre": "^0.3.0",
22
22
  "commander": "^15.0.0"
23
23
  },
24
24
  "repository": {
@@ -1,10 +1,10 @@
1
1
  # Loutre Application
2
2
 
3
- Loutreで作成した{{targetLabel}}向けApplicationです。
3
+ A Loutre Application for {{targetLabel}}.
4
4
 
5
5
  {{developmentSection}}
6
6
 
7
- ## 検証
7
+ ## Verify
8
8
 
9
9
  ```sh
10
10
  {{verifyCommand}}
@@ -2,7 +2,7 @@ import { bootstrap } from '@loutrejs/loutre/host'
2
2
  import { expect, it } from 'vitest'
3
3
  import application from './app.js'
4
4
 
5
- it('GET / がLoutre Applicationのレスポンスを返す', async () => {
5
+ it('GET / returns a response from the Loutre Application', async () => {
6
6
  const app = bootstrap({ application })
7
7
  try {
8
8
  const response = await app.fetch(new Request('http://localhost/'))
@@ -3,34 +3,26 @@ import {
3
3
  defineApplication,
4
4
  defineModule,
5
5
  implementation,
6
- procedure,
7
6
  } from '@loutrejs/loutre'
8
7
  import { http } from '@loutrejs/loutre/http'
9
8
  import { z } from 'zod'
10
-
11
- const AppContract = contract(
12
- {
13
- hello: procedure({
14
- protocols: {
15
- http: http({
16
- method: 'GET',
17
- path: '/',
18
- responses: {
19
- ok: {
20
- status: 200,
21
- body: z.object({
22
- message: z.string(),
23
- }),
24
- },
25
- },
26
- pipeline: [http.controller],
27
- }),
9
+ const AppContract = contract([
10
+ http({
11
+ hello: {
12
+ method: 'GET',
13
+ path: '/',
14
+ responses: {
15
+ ok: {
16
+ status: 200,
17
+ body: z.object({
18
+ message: z.string(),
19
+ }),
20
+ },
28
21
  },
29
- }),
30
- },
31
- { name: 'AppContract' },
32
- )
33
-
22
+ pipeline: [http.controller],
23
+ },
24
+ }),
25
+ ])
34
26
  const AppController = implementation({
35
27
  name: 'AppController',
36
28
  contract: AppContract,
@@ -43,13 +35,11 @@ const AppController = implementation({
43
35
  },
44
36
  }),
45
37
  })
46
-
47
38
  const AppModule = defineModule(() => ({
48
39
  name: 'AppModule',
49
- description: 'HTTP Applicationのentry module',
40
+ description: 'HTTP Application entry module',
50
41
  implementations: [AppController],
51
42
  }))
52
-
53
43
  export default defineApplication({
54
44
  modules: [AppModule()],
55
45
  })
@@ -1,10 +1,4 @@
1
1
  import { bunRuntime } from '@loutrejs/loutre/runtime/bun'
2
2
  import application from './app.js'
3
3
 
4
- await bunRuntime.serve({
5
- application,
6
- hostname: '127.0.0.1',
7
- port: 3000,
8
- })
9
-
10
- console.log('Loutre is swimming at http://127.0.0.1:3000')
4
+ await bunRuntime.serve({ application })
@@ -1,10 +1,4 @@
1
1
  import { denoRuntime } from '@loutrejs/loutre/runtime/deno'
2
2
  import application from './app.ts'
3
3
 
4
- await denoRuntime.serve({
5
- application,
6
- hostname: '127.0.0.1',
7
- port: 3000,
8
- })
9
-
10
- console.log('Loutre is swimming at http://127.0.0.1:3000')
4
+ await denoRuntime.serve({ application })
@@ -1,16 +1,4 @@
1
1
  import { nodeRuntime } from '@loutrejs/node'
2
2
  import application from './app.js'
3
3
 
4
- const server = await nodeRuntime.serve({
5
- application,
6
- hostname: '127.0.0.1',
7
- port: 3000,
8
- })
9
-
10
- console.log('Loutre is swimming at http://127.0.0.1:3000')
11
-
12
- for (const signal of ['SIGINT', 'SIGTERM'] as const) {
13
- process.once(signal, () => {
14
- void server.close(signal)
15
- })
16
- }
4
+ await nodeRuntime.serve({ application })