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 +65 -0
- package/dist/app.js +2 -2
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +14 -11
- package/dist/starter.js +7 -7
- package/package.json +2 -2
- package/templates/base/README.md +2 -2
- package/templates/base/src/app.test.ts +1 -1
- package/templates/base/src/app.ts +17 -27
- package/templates/targets/bun/src/main.ts +1 -7
- package/templates/targets/deno/src/main.ts +1 -7
- package/templates/targets/node/src/main.ts +1 -13
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(
|
|
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
|
|
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
|
|
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', '
|
|
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('
|
|
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
|
|
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}
|
|
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
|
|
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
|
|
157
|
-
: '<http://127.0.0.1:3000>
|
|
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
|
|
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.
|
|
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.
|
|
21
|
+
"@loutrejs/loutre": "^0.3.0",
|
|
22
22
|
"commander": "^15.0.0"
|
|
23
23
|
},
|
|
24
24
|
"repository": {
|
package/templates/base/README.md
CHANGED
|
@@ -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 /
|
|
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
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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 })
|