urur 0.1.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/LICENSE +21 -0
- package/README.md +63 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +593 -0
- package/dist/index.js.map +1 -0
- package/package.json +60 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 shampagne
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# urur
|
|
2
|
+
|
|
3
|
+
CLI for [urur.dev](https://urur.dev) - 個人開発プロダクトディレクトリ
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install -g urur
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Or run directly:
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
npx urur
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## Commands
|
|
18
|
+
|
|
19
|
+
### `urur login`
|
|
20
|
+
|
|
21
|
+
GitHub OAuth でログインします。
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
urur login
|
|
25
|
+
urur login --port 3000 # カスタムポート指定
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
### `urur submit`
|
|
29
|
+
|
|
30
|
+
プロダクトを投稿します。
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
# 対話モード
|
|
34
|
+
urur submit -i
|
|
35
|
+
|
|
36
|
+
# オプション指定
|
|
37
|
+
urur submit --name "My App" --url "https://example.com"
|
|
38
|
+
urur submit --name "My App" --url "https://example.com" --tagline "便利なツール"
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
### `urur whoami`
|
|
42
|
+
|
|
43
|
+
ログイン中のユーザー情報を表示します。
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
urur whoami
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
### `urur logout`
|
|
50
|
+
|
|
51
|
+
ログアウトして認証情報を削除します。
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
urur logout
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## Requirements
|
|
58
|
+
|
|
59
|
+
- Node.js >= 18
|
|
60
|
+
|
|
61
|
+
## License
|
|
62
|
+
|
|
63
|
+
[MIT](LICENSE)
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,593 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/program.ts
|
|
4
|
+
import { Command } from "commander";
|
|
5
|
+
|
|
6
|
+
// src/commands/login.ts
|
|
7
|
+
import { confirm, input, select } from "@inquirer/prompts";
|
|
8
|
+
import open from "open";
|
|
9
|
+
import ora from "ora";
|
|
10
|
+
import pc from "picocolors";
|
|
11
|
+
|
|
12
|
+
// src/lib/auth.ts
|
|
13
|
+
import fs from "fs/promises";
|
|
14
|
+
|
|
15
|
+
// src/lib/config.ts
|
|
16
|
+
import os from "os";
|
|
17
|
+
import path from "path";
|
|
18
|
+
var CONFIG_DIR = path.join(os.homedir(), ".urur");
|
|
19
|
+
var CREDENTIALS_PATH = path.join(CONFIG_DIR, "credentials.json");
|
|
20
|
+
var SUPABASE_URL = "https://pibznhmyybtlgmeiwrcb.supabase.co";
|
|
21
|
+
var SUPABASE_ANON_KEY = "sb_publishable_agupxY_8sff9ucTpiKf0oA_EiYFRSGD";
|
|
22
|
+
|
|
23
|
+
// src/lib/auth.ts
|
|
24
|
+
async function loadCredentials() {
|
|
25
|
+
try {
|
|
26
|
+
const data = await fs.readFile(CREDENTIALS_PATH, "utf-8");
|
|
27
|
+
return JSON.parse(data);
|
|
28
|
+
} catch {
|
|
29
|
+
return null;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
async function saveCredentials(credentials) {
|
|
33
|
+
await fs.mkdir(CONFIG_DIR, { recursive: true });
|
|
34
|
+
await fs.writeFile(CREDENTIALS_PATH, JSON.stringify(credentials, null, 2));
|
|
35
|
+
}
|
|
36
|
+
async function clearCredentials() {
|
|
37
|
+
try {
|
|
38
|
+
await fs.unlink(CREDENTIALS_PATH);
|
|
39
|
+
} catch {
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// src/lib/edgeFunctionApi.ts
|
|
44
|
+
var EDGE_FUNCTION_URL = `${SUPABASE_URL}/functions/v1/cli-auth`;
|
|
45
|
+
function buildHeaders() {
|
|
46
|
+
return {
|
|
47
|
+
"Content-Type": "application/json",
|
|
48
|
+
Authorization: `Bearer ${SUPABASE_ANON_KEY}`
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
async function requestDeviceCode() {
|
|
52
|
+
const res = await fetch(EDGE_FUNCTION_URL, {
|
|
53
|
+
method: "POST",
|
|
54
|
+
headers: buildHeaders(),
|
|
55
|
+
body: JSON.stringify({ action: "device-code" })
|
|
56
|
+
});
|
|
57
|
+
if (!res.ok) {
|
|
58
|
+
throw new Error(`Edge Function \u30A8\u30E9\u30FC: ${res.status}`);
|
|
59
|
+
}
|
|
60
|
+
return res.json();
|
|
61
|
+
}
|
|
62
|
+
async function pollToken(deviceCode) {
|
|
63
|
+
const res = await fetch(EDGE_FUNCTION_URL, {
|
|
64
|
+
method: "POST",
|
|
65
|
+
headers: buildHeaders(),
|
|
66
|
+
body: JSON.stringify({ action: "poll-token", deviceCode })
|
|
67
|
+
});
|
|
68
|
+
if (!res.ok) {
|
|
69
|
+
throw new Error(`Edge Function \u30A8\u30E9\u30FC: ${res.status}`);
|
|
70
|
+
}
|
|
71
|
+
return res.json();
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// src/lib/deviceFlow.ts
|
|
75
|
+
async function requestCode() {
|
|
76
|
+
return requestDeviceCode();
|
|
77
|
+
}
|
|
78
|
+
async function pollForSession(deviceCode, interval, expiresIn) {
|
|
79
|
+
let currentInterval = interval;
|
|
80
|
+
let elapsed = 0;
|
|
81
|
+
while (elapsed < expiresIn) {
|
|
82
|
+
await new Promise((resolve) => setTimeout(resolve, currentInterval * 1e3));
|
|
83
|
+
elapsed += currentInterval;
|
|
84
|
+
if (elapsed >= expiresIn) {
|
|
85
|
+
break;
|
|
86
|
+
}
|
|
87
|
+
const response = await pollToken(deviceCode);
|
|
88
|
+
switch (response.status) {
|
|
89
|
+
case "success":
|
|
90
|
+
return response.session;
|
|
91
|
+
case "pending":
|
|
92
|
+
continue;
|
|
93
|
+
case "slow_down":
|
|
94
|
+
currentInterval += 5;
|
|
95
|
+
continue;
|
|
96
|
+
case "expired":
|
|
97
|
+
throw new Error(
|
|
98
|
+
"\u8A8D\u8A3C\u306E\u6709\u52B9\u671F\u9650\u304C\u5207\u308C\u307E\u3057\u305F\u3002\u518D\u5EA6 urur login \u3092\u5B9F\u884C\u3057\u3066\u304F\u3060\u3055\u3044\u3002"
|
|
99
|
+
);
|
|
100
|
+
case "error":
|
|
101
|
+
throw new Error(response.message);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
throw new Error(
|
|
105
|
+
"\u8A8D\u8A3C\u304C\u30BF\u30A4\u30E0\u30A2\u30A6\u30C8\u3057\u307E\u3057\u305F\u3002\u518D\u5EA6 urur login \u3092\u5B9F\u884C\u3057\u3066\u304F\u3060\u3055\u3044\u3002"
|
|
106
|
+
);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// src/lib/supabase.ts
|
|
110
|
+
import { createClient } from "@supabase/supabase-js";
|
|
111
|
+
function createMemoryStorage() {
|
|
112
|
+
const store = /* @__PURE__ */ new Map();
|
|
113
|
+
return {
|
|
114
|
+
getItem(key) {
|
|
115
|
+
return store.get(key) ?? null;
|
|
116
|
+
},
|
|
117
|
+
setItem(key, value) {
|
|
118
|
+
store.set(key, value);
|
|
119
|
+
},
|
|
120
|
+
removeItem(key) {
|
|
121
|
+
store.delete(key);
|
|
122
|
+
}
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
function getSupabaseClient() {
|
|
126
|
+
return createClient(SUPABASE_URL, SUPABASE_ANON_KEY, {
|
|
127
|
+
auth: {
|
|
128
|
+
storage: createMemoryStorage(),
|
|
129
|
+
autoRefreshToken: false,
|
|
130
|
+
detectSessionInUrl: false
|
|
131
|
+
}
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// src/lib/magicLink.ts
|
|
136
|
+
async function sendOtp(email) {
|
|
137
|
+
const supabase = getSupabaseClient();
|
|
138
|
+
const { error } = await supabase.auth.signInWithOtp({ email });
|
|
139
|
+
if (error) {
|
|
140
|
+
throw new Error(error.message);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
async function verifyOtp(email, token) {
|
|
144
|
+
const supabase = getSupabaseClient();
|
|
145
|
+
const { data, error } = await supabase.auth.verifyOtp({
|
|
146
|
+
email,
|
|
147
|
+
token,
|
|
148
|
+
type: "email"
|
|
149
|
+
});
|
|
150
|
+
if (error) {
|
|
151
|
+
throw new Error(error.message);
|
|
152
|
+
}
|
|
153
|
+
if (!data.session) {
|
|
154
|
+
throw new Error("\u30BB\u30C3\u30B7\u30E7\u30F3\u304C\u53D6\u5F97\u3067\u304D\u307E\u305B\u3093\u3067\u3057\u305F");
|
|
155
|
+
}
|
|
156
|
+
return {
|
|
157
|
+
accessToken: data.session.access_token,
|
|
158
|
+
refreshToken: data.session.refresh_token,
|
|
159
|
+
expiresAt: data.session.expires_at ?? 0,
|
|
160
|
+
email: data.session.user?.email ?? email
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// src/commands/login.ts
|
|
165
|
+
var MAX_OTP_ATTEMPTS = 3;
|
|
166
|
+
async function login() {
|
|
167
|
+
const supabase = getSupabaseClient();
|
|
168
|
+
const existing = await loadCredentials();
|
|
169
|
+
if (existing) {
|
|
170
|
+
await supabase.auth.setSession({
|
|
171
|
+
access_token: existing.access_token,
|
|
172
|
+
refresh_token: existing.refresh_token
|
|
173
|
+
});
|
|
174
|
+
const { data } = await supabase.auth.getUser();
|
|
175
|
+
const username = data?.user?.user_metadata?.user_name ?? "\u4E0D\u660E";
|
|
176
|
+
console.log(pc.green(`\u65E2\u306B\u30ED\u30B0\u30A4\u30F3\u6E08\u307F\u3067\u3059: ${username}`));
|
|
177
|
+
const shouldRelogin = await confirm({
|
|
178
|
+
message: "\u518D\u30ED\u30B0\u30A4\u30F3\u3057\u307E\u3059\u304B\uFF1F",
|
|
179
|
+
default: false
|
|
180
|
+
});
|
|
181
|
+
if (!shouldRelogin) {
|
|
182
|
+
return;
|
|
183
|
+
}
|
|
184
|
+
await clearCredentials();
|
|
185
|
+
}
|
|
186
|
+
const method = await select({
|
|
187
|
+
message: "\u30ED\u30B0\u30A4\u30F3\u65B9\u6CD5\u3092\u9078\u629E\u3057\u3066\u304F\u3060\u3055\u3044",
|
|
188
|
+
choices: [
|
|
189
|
+
{ name: "GitHub", value: "github" },
|
|
190
|
+
{ name: "\u30E1\u30FC\u30EB\u30A2\u30C9\u30EC\u30B9", value: "email" }
|
|
191
|
+
]
|
|
192
|
+
});
|
|
193
|
+
if (method === "github") {
|
|
194
|
+
await loginWithGitHub();
|
|
195
|
+
} else {
|
|
196
|
+
await loginWithEmail();
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
async function loginWithGitHub() {
|
|
200
|
+
try {
|
|
201
|
+
const deviceCode = await requestCode();
|
|
202
|
+
console.log();
|
|
203
|
+
console.log(pc.bold(`\u8A8D\u8A3C\u30B3\u30FC\u30C9: ${pc.cyan(deviceCode.userCode)}`));
|
|
204
|
+
console.log(
|
|
205
|
+
`\u4EE5\u4E0B\u306E URL \u3067\u30B3\u30FC\u30C9\u3092\u5165\u529B\u3057\u3066\u304F\u3060\u3055\u3044: ${deviceCode.verificationUri}`
|
|
206
|
+
);
|
|
207
|
+
console.log();
|
|
208
|
+
await open(deviceCode.verificationUri);
|
|
209
|
+
const spinner = ora("GitHub \u3067\u8A8D\u8A3C\u3092\u5F85\u3063\u3066\u3044\u307E\u3059...").start();
|
|
210
|
+
try {
|
|
211
|
+
const session = await pollForSession(
|
|
212
|
+
deviceCode.deviceCode,
|
|
213
|
+
deviceCode.interval,
|
|
214
|
+
deviceCode.expiresIn
|
|
215
|
+
);
|
|
216
|
+
spinner.succeed("\u8A8D\u8A3C\u5B8C\u4E86");
|
|
217
|
+
await saveCredentials({
|
|
218
|
+
access_token: session.accessToken,
|
|
219
|
+
refresh_token: session.refreshToken,
|
|
220
|
+
expires_at: session.expiresAt
|
|
221
|
+
});
|
|
222
|
+
console.log(pc.green(`\u30ED\u30B0\u30A4\u30F3\u6210\u529F: ${session.userName}`));
|
|
223
|
+
} catch (err) {
|
|
224
|
+
spinner.fail("\u8A8D\u8A3C\u306B\u5931\u6557\u3057\u307E\u3057\u305F");
|
|
225
|
+
throw err;
|
|
226
|
+
}
|
|
227
|
+
} catch (err) {
|
|
228
|
+
console.log(
|
|
229
|
+
pc.red(`\u30A8\u30E9\u30FC: ${err instanceof Error ? err.message : String(err)}`)
|
|
230
|
+
);
|
|
231
|
+
process.exitCode = 1;
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
async function loginWithEmail() {
|
|
235
|
+
try {
|
|
236
|
+
const email = await input({
|
|
237
|
+
message: "\u30E1\u30FC\u30EB\u30A2\u30C9\u30EC\u30B9\u3092\u5165\u529B\u3057\u3066\u304F\u3060\u3055\u3044",
|
|
238
|
+
validate: (value) => {
|
|
239
|
+
if (!value.includes("@")) {
|
|
240
|
+
return "\u6709\u52B9\u306A\u30E1\u30FC\u30EB\u30A2\u30C9\u30EC\u30B9\u3092\u5165\u529B\u3057\u3066\u304F\u3060\u3055\u3044";
|
|
241
|
+
}
|
|
242
|
+
return true;
|
|
243
|
+
}
|
|
244
|
+
});
|
|
245
|
+
await sendOtp(email);
|
|
246
|
+
console.log(pc.green(`${email} \u306B\u8A8D\u8A3C\u30B3\u30FC\u30C9\u3092\u9001\u4FE1\u3057\u307E\u3057\u305F`));
|
|
247
|
+
const session = await attemptOtpVerification(email);
|
|
248
|
+
if (session) {
|
|
249
|
+
await saveCredentials({
|
|
250
|
+
access_token: session.accessToken,
|
|
251
|
+
refresh_token: session.refreshToken,
|
|
252
|
+
expires_at: session.expiresAt
|
|
253
|
+
});
|
|
254
|
+
console.log(pc.green(`\u30ED\u30B0\u30A4\u30F3\u6210\u529F: ${session.email}`));
|
|
255
|
+
}
|
|
256
|
+
} catch (err) {
|
|
257
|
+
console.log(
|
|
258
|
+
pc.red(`\u30A8\u30E9\u30FC: ${err instanceof Error ? err.message : String(err)}`)
|
|
259
|
+
);
|
|
260
|
+
process.exitCode = 1;
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
async function attemptOtpVerification(email) {
|
|
264
|
+
for (let attempt = 0; attempt < MAX_OTP_ATTEMPTS; attempt++) {
|
|
265
|
+
const token = await input({
|
|
266
|
+
message: "\u8A8D\u8A3C\u30B3\u30FC\u30C9\u3092\u5165\u529B\u3057\u3066\u304F\u3060\u3055\u3044"
|
|
267
|
+
});
|
|
268
|
+
try {
|
|
269
|
+
return await verifyOtp(email, token);
|
|
270
|
+
} catch {
|
|
271
|
+
const remaining = MAX_OTP_ATTEMPTS - attempt - 1;
|
|
272
|
+
if (remaining > 0) {
|
|
273
|
+
console.log(
|
|
274
|
+
pc.yellow(`\u8A8D\u8A3C\u30B3\u30FC\u30C9\u304C\u7121\u52B9\u3067\u3059\u3002\u6B8B\u308A ${remaining} \u56DE\u8A66\u884C\u3067\u304D\u307E\u3059\u3002`)
|
|
275
|
+
);
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
const shouldResend = await confirm({
|
|
280
|
+
message: "\u8A8D\u8A3C\u30B3\u30FC\u30C9\u3092\u518D\u9001\u4FE1\u3057\u307E\u3059\u304B\uFF1F",
|
|
281
|
+
default: true
|
|
282
|
+
});
|
|
283
|
+
if (shouldResend) {
|
|
284
|
+
await sendOtp(email);
|
|
285
|
+
console.log(pc.green(`${email} \u306B\u8A8D\u8A3C\u30B3\u30FC\u30C9\u3092\u518D\u9001\u4FE1\u3057\u307E\u3057\u305F`));
|
|
286
|
+
const token = await input({
|
|
287
|
+
message: "\u8A8D\u8A3C\u30B3\u30FC\u30C9\u3092\u5165\u529B\u3057\u3066\u304F\u3060\u3055\u3044"
|
|
288
|
+
});
|
|
289
|
+
return await verifyOtp(email, token);
|
|
290
|
+
}
|
|
291
|
+
return null;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
// src/commands/logout.ts
|
|
295
|
+
import pc2 from "picocolors";
|
|
296
|
+
async function logout() {
|
|
297
|
+
const credentials = await loadCredentials();
|
|
298
|
+
if (!credentials) {
|
|
299
|
+
console.log(pc2.yellow("\u30ED\u30B0\u30A4\u30F3\u3057\u3066\u3044\u307E\u305B\u3093\u3002"));
|
|
300
|
+
return;
|
|
301
|
+
}
|
|
302
|
+
await clearCredentials();
|
|
303
|
+
console.log(pc2.green("\u30ED\u30B0\u30A2\u30A6\u30C8\u3057\u307E\u3057\u305F\u3002"));
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
// src/commands/submit.ts
|
|
307
|
+
import { confirm as confirm2, input as input2 } from "@inquirer/prompts";
|
|
308
|
+
import ora2 from "ora";
|
|
309
|
+
import pc3 from "picocolors";
|
|
310
|
+
|
|
311
|
+
// src/lib/validation.ts
|
|
312
|
+
var ERROR_MESSAGES = {
|
|
313
|
+
name: {
|
|
314
|
+
required: "\u30D7\u30ED\u30C0\u30AF\u30C8\u540D\u306F\u5FC5\u9808\u3067\u3059",
|
|
315
|
+
maxLength: "\u30D7\u30ED\u30C0\u30AF\u30C8\u540D\u306F100\u6587\u5B57\u4EE5\u5185\u3067\u5165\u529B\u3057\u3066\u304F\u3060\u3055\u3044"
|
|
316
|
+
},
|
|
317
|
+
url: {
|
|
318
|
+
required: "URL\u306F\u5FC5\u9808\u3067\u3059",
|
|
319
|
+
invalid: "\u6709\u52B9\u306AURL\u3092\u5165\u529B\u3057\u3066\u304F\u3060\u3055\u3044"
|
|
320
|
+
},
|
|
321
|
+
tagline: {
|
|
322
|
+
maxLength: "\u30BF\u30B0\u30E9\u30A4\u30F3\u306F200\u6587\u5B57\u4EE5\u5185\u3067\u5165\u529B\u3057\u3066\u304F\u3060\u3055\u3044"
|
|
323
|
+
},
|
|
324
|
+
description: {
|
|
325
|
+
maxLength: "\u8AAC\u660E\u306F2000\u6587\u5B57\u4EE5\u5185\u3067\u5165\u529B\u3057\u3066\u304F\u3060\u3055\u3044"
|
|
326
|
+
},
|
|
327
|
+
logo_url: {
|
|
328
|
+
invalid: "\u6709\u52B9\u306AURL\u3092\u5165\u529B\u3057\u3066\u304F\u3060\u3055\u3044"
|
|
329
|
+
}
|
|
330
|
+
};
|
|
331
|
+
var VALIDATION_RULES = {
|
|
332
|
+
name: { required: true, maxLength: 100 },
|
|
333
|
+
url: { required: true },
|
|
334
|
+
tagline: { maxLength: 200 },
|
|
335
|
+
description: { maxLength: 2e3 }
|
|
336
|
+
};
|
|
337
|
+
function isValidUrl(value) {
|
|
338
|
+
try {
|
|
339
|
+
new URL(value);
|
|
340
|
+
return true;
|
|
341
|
+
} catch {
|
|
342
|
+
return false;
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
function validateName(value) {
|
|
346
|
+
const trimmed = value.trim();
|
|
347
|
+
if (!trimmed) {
|
|
348
|
+
return ERROR_MESSAGES.name.required;
|
|
349
|
+
}
|
|
350
|
+
if (trimmed.length > VALIDATION_RULES.name.maxLength) {
|
|
351
|
+
return ERROR_MESSAGES.name.maxLength;
|
|
352
|
+
}
|
|
353
|
+
return null;
|
|
354
|
+
}
|
|
355
|
+
function validateUrl(value) {
|
|
356
|
+
const trimmed = value.trim();
|
|
357
|
+
if (!trimmed) {
|
|
358
|
+
return ERROR_MESSAGES.url.required;
|
|
359
|
+
}
|
|
360
|
+
if (!isValidUrl(trimmed)) {
|
|
361
|
+
return ERROR_MESSAGES.url.invalid;
|
|
362
|
+
}
|
|
363
|
+
return null;
|
|
364
|
+
}
|
|
365
|
+
function validateTagline(value) {
|
|
366
|
+
if (!value) {
|
|
367
|
+
return null;
|
|
368
|
+
}
|
|
369
|
+
if (value.length > VALIDATION_RULES.tagline.maxLength) {
|
|
370
|
+
return ERROR_MESSAGES.tagline.maxLength;
|
|
371
|
+
}
|
|
372
|
+
return null;
|
|
373
|
+
}
|
|
374
|
+
function validateDescription(value) {
|
|
375
|
+
if (!value) {
|
|
376
|
+
return null;
|
|
377
|
+
}
|
|
378
|
+
if (value.length > VALIDATION_RULES.description.maxLength) {
|
|
379
|
+
return ERROR_MESSAGES.description.maxLength;
|
|
380
|
+
}
|
|
381
|
+
return null;
|
|
382
|
+
}
|
|
383
|
+
function validateLogoUrl(value) {
|
|
384
|
+
if (!value) {
|
|
385
|
+
return null;
|
|
386
|
+
}
|
|
387
|
+
if (!isValidUrl(value)) {
|
|
388
|
+
return ERROR_MESSAGES.logo_url.invalid;
|
|
389
|
+
}
|
|
390
|
+
return null;
|
|
391
|
+
}
|
|
392
|
+
function validateAll(data) {
|
|
393
|
+
const errors = {};
|
|
394
|
+
const nameError = validateName(data.name ?? "");
|
|
395
|
+
if (nameError) {
|
|
396
|
+
errors.name = nameError;
|
|
397
|
+
}
|
|
398
|
+
const urlError = validateUrl(data.url ?? "");
|
|
399
|
+
if (urlError) {
|
|
400
|
+
errors.url = urlError;
|
|
401
|
+
}
|
|
402
|
+
const taglineError = validateTagline(data.tagline);
|
|
403
|
+
if (taglineError) {
|
|
404
|
+
errors.tagline = taglineError;
|
|
405
|
+
}
|
|
406
|
+
const descriptionError = validateDescription(data.description);
|
|
407
|
+
if (descriptionError) {
|
|
408
|
+
errors.description = descriptionError;
|
|
409
|
+
}
|
|
410
|
+
const logoUrlError = validateLogoUrl(data.logo_url);
|
|
411
|
+
if (logoUrlError) {
|
|
412
|
+
errors.logo_url = logoUrlError;
|
|
413
|
+
}
|
|
414
|
+
return {
|
|
415
|
+
valid: Object.keys(errors).length === 0,
|
|
416
|
+
errors
|
|
417
|
+
};
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
// src/commands/submit.ts
|
|
421
|
+
function shouldUseInteractive(options) {
|
|
422
|
+
return options.interactive === true || !options.name || !options.url;
|
|
423
|
+
}
|
|
424
|
+
async function promptServiceData() {
|
|
425
|
+
const name = await input2({
|
|
426
|
+
message: "\u30D7\u30ED\u30C0\u30AF\u30C8\u540D\uFF08\u5FC5\u9808\uFF09:",
|
|
427
|
+
validate: (value) => validateName(value) ?? true
|
|
428
|
+
});
|
|
429
|
+
const url = await input2({
|
|
430
|
+
message: "\u30D7\u30ED\u30C0\u30AF\u30C8URL\uFF08\u5FC5\u9808\uFF09:",
|
|
431
|
+
validate: (value) => validateUrl(value) ?? true
|
|
432
|
+
});
|
|
433
|
+
const tagline = await input2({
|
|
434
|
+
message: "\u30BF\u30B0\u30E9\u30A4\u30F3\uFF08\u7701\u7565\u53EF\uFF09:",
|
|
435
|
+
validate: (value) => {
|
|
436
|
+
if (!value) return true;
|
|
437
|
+
return validateTagline(value) ?? true;
|
|
438
|
+
}
|
|
439
|
+
});
|
|
440
|
+
const description = await input2({
|
|
441
|
+
message: "\u8AAC\u660E\uFF08\u7701\u7565\u53EF\uFF09:",
|
|
442
|
+
validate: (value) => {
|
|
443
|
+
if (!value) return true;
|
|
444
|
+
return validateDescription(value) ?? true;
|
|
445
|
+
}
|
|
446
|
+
});
|
|
447
|
+
const logo_url = await input2({
|
|
448
|
+
message: "\u30ED\u30B4URL\uFF08\u7701\u7565\u53EF\uFF09:",
|
|
449
|
+
validate: (value) => {
|
|
450
|
+
if (!value) return true;
|
|
451
|
+
return validateLogoUrl(value) ?? true;
|
|
452
|
+
}
|
|
453
|
+
});
|
|
454
|
+
return {
|
|
455
|
+
name,
|
|
456
|
+
url,
|
|
457
|
+
tagline: tagline || void 0,
|
|
458
|
+
description: description || void 0,
|
|
459
|
+
logo_url: logo_url || void 0
|
|
460
|
+
};
|
|
461
|
+
}
|
|
462
|
+
function displaySummary(data) {
|
|
463
|
+
console.log(pc3.bold("\n\u6295\u7A3F\u5185\u5BB9:"));
|
|
464
|
+
console.log(` \u30D7\u30ED\u30C0\u30AF\u30C8\u540D: ${data.name}`);
|
|
465
|
+
console.log(` URL: ${data.url}`);
|
|
466
|
+
if (data.tagline) console.log(` \u30BF\u30B0\u30E9\u30A4\u30F3: ${data.tagline}`);
|
|
467
|
+
if (data.description) console.log(` \u8AAC\u660E: ${data.description}`);
|
|
468
|
+
if (data.logo_url) console.log(` \u30ED\u30B4URL: ${data.logo_url}`);
|
|
469
|
+
console.log("");
|
|
470
|
+
}
|
|
471
|
+
function displayErrors(errors) {
|
|
472
|
+
console.log(pc3.red("\n\u30D0\u30EA\u30C7\u30FC\u30B7\u30E7\u30F3\u30A8\u30E9\u30FC:"));
|
|
473
|
+
for (const [, message] of Object.entries(errors)) {
|
|
474
|
+
console.log(pc3.red(` - ${message}`));
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
async function submit(options) {
|
|
478
|
+
const credentials = await loadCredentials();
|
|
479
|
+
if (!credentials) {
|
|
480
|
+
console.log(pc3.red("\u30ED\u30B0\u30A4\u30F3\u304C\u5FC5\u8981\u3067\u3059\u3002`urur login` \u3092\u5B9F\u884C\u3057\u3066\u304F\u3060\u3055\u3044\u3002"));
|
|
481
|
+
process.exitCode = 1;
|
|
482
|
+
return;
|
|
483
|
+
}
|
|
484
|
+
const supabase = getSupabaseClient();
|
|
485
|
+
await supabase.auth.setSession({
|
|
486
|
+
access_token: credentials.access_token,
|
|
487
|
+
refresh_token: credentials.refresh_token
|
|
488
|
+
});
|
|
489
|
+
const { data: userData, error: userError } = await supabase.auth.getUser();
|
|
490
|
+
if (userError || !userData.user) {
|
|
491
|
+
console.log(
|
|
492
|
+
pc3.red(
|
|
493
|
+
`\u8A8D\u8A3C\u30A8\u30E9\u30FC: ${userError?.message ?? "\u30E6\u30FC\u30B6\u30FC\u60C5\u5831\u3092\u53D6\u5F97\u3067\u304D\u307E\u305B\u3093\u3067\u3057\u305F"}\u3002\`urur login\` \u3067\u518D\u30ED\u30B0\u30A4\u30F3\u3057\u3066\u304F\u3060\u3055\u3044\u3002`
|
|
494
|
+
)
|
|
495
|
+
);
|
|
496
|
+
process.exitCode = 1;
|
|
497
|
+
return;
|
|
498
|
+
}
|
|
499
|
+
let formData;
|
|
500
|
+
if (shouldUseInteractive(options)) {
|
|
501
|
+
formData = await promptServiceData();
|
|
502
|
+
} else {
|
|
503
|
+
formData = {
|
|
504
|
+
name: options.name,
|
|
505
|
+
url: options.url,
|
|
506
|
+
tagline: options.tagline,
|
|
507
|
+
description: options.description,
|
|
508
|
+
logo_url: options.logoUrl
|
|
509
|
+
};
|
|
510
|
+
const result = validateAll(formData);
|
|
511
|
+
if (!result.valid) {
|
|
512
|
+
displayErrors(result.errors);
|
|
513
|
+
process.exitCode = 1;
|
|
514
|
+
return;
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
displaySummary(formData);
|
|
518
|
+
const confirmed = await confirm2({
|
|
519
|
+
message: "\u6295\u7A3F\u3057\u307E\u3059\u304B\uFF1F",
|
|
520
|
+
default: true
|
|
521
|
+
});
|
|
522
|
+
if (!confirmed) {
|
|
523
|
+
console.log(pc3.yellow("\u6295\u7A3F\u3092\u30AD\u30E3\u30F3\u30BB\u30EB\u3057\u307E\u3057\u305F\u3002"));
|
|
524
|
+
return;
|
|
525
|
+
}
|
|
526
|
+
const spinner = ora2("\u6295\u7A3F\u4E2D...").start();
|
|
527
|
+
const { error: insertError } = await supabase.from("services").insert({
|
|
528
|
+
user_id: userData.user.id,
|
|
529
|
+
name: formData.name,
|
|
530
|
+
url: formData.url,
|
|
531
|
+
tagline: formData.tagline ?? null,
|
|
532
|
+
description: formData.description ?? null,
|
|
533
|
+
logo_url: formData.logo_url ?? null,
|
|
534
|
+
source: "cli"
|
|
535
|
+
}).select().single();
|
|
536
|
+
if (insertError) {
|
|
537
|
+
spinner.fail();
|
|
538
|
+
console.log(pc3.red(`\u6295\u7A3F\u30A8\u30E9\u30FC: ${insertError.message}`));
|
|
539
|
+
process.exitCode = 1;
|
|
540
|
+
return;
|
|
541
|
+
}
|
|
542
|
+
spinner.succeed();
|
|
543
|
+
console.log(pc3.green(`\u300C${formData.name}\u300D\u3092\u6295\u7A3F\u3057\u307E\u3057\u305F\uFF01`));
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
// src/commands/whoami.ts
|
|
547
|
+
import pc4 from "picocolors";
|
|
548
|
+
async function whoami() {
|
|
549
|
+
console.log(
|
|
550
|
+
pc4.yellow("\u26A0 urur whoami \u306F\u672A\u5B9F\u88C5\u3067\u3059\u3002Kiro spec\u3067\u5B9F\u88C5\u3092\u9032\u3081\u3066\u304F\u3060\u3055\u3044\u3002")
|
|
551
|
+
);
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
// src/program.ts
|
|
555
|
+
function createProgram() {
|
|
556
|
+
const program2 = new Command();
|
|
557
|
+
program2.name("urur").description("CLI for urur.dev - \u500B\u4EBA\u958B\u767A\u30D7\u30ED\u30C0\u30AF\u30C8\u30C7\u30A3\u30EC\u30AF\u30C8\u30EA").version("0.1.0");
|
|
558
|
+
program2.command("login").description("GitHub\u307E\u305F\u306F\u30E1\u30FC\u30EB\u30A2\u30C9\u30EC\u30B9\u3067\u30ED\u30B0\u30A4\u30F3").addHelpText(
|
|
559
|
+
"after",
|
|
560
|
+
`
|
|
561
|
+
\u4F7F\u7528\u4F8B:
|
|
562
|
+
$ urur login
|
|
563
|
+
|
|
564
|
+
GitHub Device Flow \u307E\u305F\u306F\u30E1\u30FC\u30EB\u30A2\u30C9\u30EC\u30B9\uFF08Magic Link\uFF09\u3067\u8A8D\u8A3C\u3057\u307E\u3059\u3002`
|
|
565
|
+
).action(login);
|
|
566
|
+
program2.command("logout").description("\u30ED\u30B0\u30A2\u30A6\u30C8\uFF08\u8A8D\u8A3C\u60C5\u5831\u3092\u524A\u9664\uFF09").addHelpText(
|
|
567
|
+
"after",
|
|
568
|
+
`
|
|
569
|
+
~/.urur/credentials.json \u3092\u524A\u9664\u3057\u307E\u3059\u3002`
|
|
570
|
+
).action(logout);
|
|
571
|
+
program2.command("submit").description("\u30D7\u30ED\u30C0\u30AF\u30C8\u3092\u6295\u7A3F").option("--name <name>", "\u30D7\u30ED\u30C0\u30AF\u30C8\u540D\uFF08\u5FC5\u9808\uFF09").option("--url <url>", "\u30D7\u30ED\u30C0\u30AF\u30C8URL\uFF08\u5FC5\u9808\uFF09").option("--tagline <text>", "\u30BF\u30B0\u30E9\u30A4\u30F3\uFF08200\u6587\u5B57\u4EE5\u5185\uFF09").option("--description <text>", "\u8AAC\u660E\uFF082000\u6587\u5B57\u4EE5\u5185\uFF09").option("--logo-url <url>", "\u30ED\u30B4URL").option("-i, --interactive", "\u5BFE\u8A71\u30E2\u30FC\u30C9\u3067\u5165\u529B").addHelpText(
|
|
572
|
+
"after",
|
|
573
|
+
`
|
|
574
|
+
\u4F7F\u7528\u4F8B:
|
|
575
|
+
$ urur submit -i
|
|
576
|
+
$ urur submit --name "My App" --url "https://example.com"
|
|
577
|
+
$ urur submit --name "My App" --url "https://example.com" --tagline "\u4FBF\u5229\u306A\u30C4\u30FC\u30EB"
|
|
578
|
+
|
|
579
|
+
\u30AA\u30D7\u30B7\u30E7\u30F3\u672A\u6307\u5B9A\u306E\u5834\u5408\u306F\u5BFE\u8A71\u30E2\u30FC\u30C9\u3067\u5165\u529B\u3057\u307E\u3059\u3002`
|
|
580
|
+
).action(submit);
|
|
581
|
+
program2.command("whoami").description("\u30ED\u30B0\u30A4\u30F3\u4E2D\u306E\u30E6\u30FC\u30B6\u30FC\u60C5\u5831\u3092\u8868\u793A").addHelpText(
|
|
582
|
+
"after",
|
|
583
|
+
`
|
|
584
|
+
GitHub\u30E6\u30FC\u30B6\u30FC\u540D\u3068\u30E1\u30FC\u30EB\u30A2\u30C9\u30EC\u30B9\u3092\u8868\u793A\u3057\u307E\u3059\u3002
|
|
585
|
+
\u672A\u30ED\u30B0\u30A4\u30F3\u306E\u5834\u5408\u306F "urur login" \u3092\u5B9F\u884C\u3057\u3066\u304F\u3060\u3055\u3044\u3002`
|
|
586
|
+
).action(whoami);
|
|
587
|
+
return program2;
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
// src/index.ts
|
|
591
|
+
var program = createProgram();
|
|
592
|
+
program.parse();
|
|
593
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/program.ts","../src/commands/login.ts","../src/lib/auth.ts","../src/lib/config.ts","../src/lib/edgeFunctionApi.ts","../src/lib/deviceFlow.ts","../src/lib/supabase.ts","../src/lib/magicLink.ts","../src/commands/logout.ts","../src/commands/submit.ts","../src/lib/validation.ts","../src/commands/whoami.ts","../src/index.ts"],"sourcesContent":["import { Command } from 'commander'\nimport { login } from './commands/login.js'\nimport { logout } from './commands/logout.js'\nimport { submit } from './commands/submit.js'\nimport { whoami } from './commands/whoami.js'\n\nexport function createProgram(): Command {\n const program = new Command()\n\n program\n .name('urur')\n .description('CLI for urur.dev - 個人開発プロダクトディレクトリ')\n .version('0.1.0')\n\n program\n .command('login')\n .description('GitHubまたはメールアドレスでログイン')\n .addHelpText(\n 'after',\n `\n使用例:\n $ urur login\n\nGitHub Device Flow またはメールアドレス(Magic Link)で認証します。`,\n )\n .action(login)\n\n program\n .command('logout')\n .description('ログアウト(認証情報を削除)')\n .addHelpText(\n 'after',\n `\n~/.urur/credentials.json を削除します。`,\n )\n .action(logout)\n\n program\n .command('submit')\n .description('プロダクトを投稿')\n .option('--name <name>', 'プロダクト名(必須)')\n .option('--url <url>', 'プロダクトURL(必須)')\n .option('--tagline <text>', 'タグライン(200文字以内)')\n .option('--description <text>', '説明(2000文字以内)')\n .option('--logo-url <url>', 'ロゴURL')\n .option('-i, --interactive', '対話モードで入力')\n .addHelpText(\n 'after',\n `\n使用例:\n $ urur submit -i\n $ urur submit --name \"My App\" --url \"https://example.com\"\n $ urur submit --name \"My App\" --url \"https://example.com\" --tagline \"便利なツール\"\n\nオプション未指定の場合は対話モードで入力します。`,\n )\n .action(submit)\n\n program\n .command('whoami')\n .description('ログイン中のユーザー情報を表示')\n .addHelpText(\n 'after',\n `\nGitHubユーザー名とメールアドレスを表示します。\n未ログインの場合は \"urur login\" を実行してください。`,\n )\n .action(whoami)\n\n return program\n}\n","import { confirm, input, select } from '@inquirer/prompts'\nimport open from 'open'\nimport ora from 'ora'\nimport pc from 'picocolors'\nimport {\n clearCredentials,\n loadCredentials,\n saveCredentials,\n} from '../lib/auth.js'\nimport { pollForSession, requestCode } from '../lib/deviceFlow.js'\nimport { sendOtp, verifyOtp } from '../lib/magicLink.js'\nimport { getSupabaseClient } from '../lib/supabase.js'\n\nconst MAX_OTP_ATTEMPTS = 3\n\nexport async function login(): Promise<void> {\n const supabase = getSupabaseClient()\n\n // Check existing credentials\n const existing = await loadCredentials()\n if (existing) {\n await supabase.auth.setSession({\n access_token: existing.access_token,\n refresh_token: existing.refresh_token,\n })\n const { data } = await supabase.auth.getUser()\n const username = data?.user?.user_metadata?.user_name ?? '不明'\n\n console.log(pc.green(`既にログイン済みです: ${username}`))\n\n const shouldRelogin = await confirm({\n message: '再ログインしますか?',\n default: false,\n })\n\n if (!shouldRelogin) {\n return\n }\n\n await clearCredentials()\n }\n\n // Select login method\n const method = await select({\n message: 'ログイン方法を選択してください',\n choices: [\n { name: 'GitHub', value: 'github' },\n { name: 'メールアドレス', value: 'email' },\n ],\n })\n\n if (method === 'github') {\n await loginWithGitHub()\n } else {\n await loginWithEmail()\n }\n}\n\nasync function loginWithGitHub(): Promise<void> {\n try {\n const deviceCode = await requestCode()\n\n console.log()\n console.log(pc.bold(`認証コード: ${pc.cyan(deviceCode.userCode)}`))\n console.log(\n `以下の URL でコードを入力してください: ${deviceCode.verificationUri}`,\n )\n console.log()\n\n await open(deviceCode.verificationUri)\n\n const spinner = ora('GitHub で認証を待っています...').start()\n\n try {\n const session = await pollForSession(\n deviceCode.deviceCode,\n deviceCode.interval,\n deviceCode.expiresIn,\n )\n\n spinner.succeed('認証完了')\n\n await saveCredentials({\n access_token: session.accessToken,\n refresh_token: session.refreshToken,\n expires_at: session.expiresAt,\n })\n\n console.log(pc.green(`ログイン成功: ${session.userName}`))\n } catch (err) {\n spinner.fail('認証に失敗しました')\n throw err\n }\n } catch (err) {\n console.log(\n pc.red(`エラー: ${err instanceof Error ? err.message : String(err)}`),\n )\n process.exitCode = 1\n }\n}\n\nasync function loginWithEmail(): Promise<void> {\n try {\n const email = await input({\n message: 'メールアドレスを入力してください',\n validate: (value) => {\n if (!value.includes('@')) {\n return '有効なメールアドレスを入力してください'\n }\n return true\n },\n })\n\n await sendOtp(email)\n console.log(pc.green(`${email} に認証コードを送信しました`))\n\n const session = await attemptOtpVerification(email)\n\n if (session) {\n await saveCredentials({\n access_token: session.accessToken,\n refresh_token: session.refreshToken,\n expires_at: session.expiresAt,\n })\n\n console.log(pc.green(`ログイン成功: ${session.email}`))\n }\n } catch (err) {\n console.log(\n pc.red(`エラー: ${err instanceof Error ? err.message : String(err)}`),\n )\n process.exitCode = 1\n }\n}\n\nasync function attemptOtpVerification(email: string): Promise<{\n accessToken: string\n refreshToken: string\n expiresAt: number\n email: string\n} | null> {\n for (let attempt = 0; attempt < MAX_OTP_ATTEMPTS; attempt++) {\n const token = await input({\n message: '認証コードを入力してください',\n })\n\n try {\n return await verifyOtp(email, token)\n } catch {\n const remaining = MAX_OTP_ATTEMPTS - attempt - 1\n if (remaining > 0) {\n console.log(\n pc.yellow(`認証コードが無効です。残り ${remaining} 回試行できます。`),\n )\n }\n }\n }\n\n // All attempts failed, offer resend\n const shouldResend = await confirm({\n message: '認証コードを再送信しますか?',\n default: true,\n })\n\n if (shouldResend) {\n await sendOtp(email)\n console.log(pc.green(`${email} に認証コードを再送信しました`))\n\n const token = await input({\n message: '認証コードを入力してください',\n })\n\n return await verifyOtp(email, token)\n }\n\n return null\n}\n","import fs from 'node:fs/promises'\nimport { CONFIG_DIR, CREDENTIALS_PATH } from './config.js'\n\nexport interface Credentials {\n access_token: string\n refresh_token: string\n expires_at: number\n}\n\nexport async function loadCredentials(): Promise<Credentials | null> {\n try {\n const data = await fs.readFile(CREDENTIALS_PATH, 'utf-8')\n return JSON.parse(data) as Credentials\n } catch {\n return null\n }\n}\n\nexport async function saveCredentials(credentials: Credentials): Promise<void> {\n await fs.mkdir(CONFIG_DIR, { recursive: true })\n await fs.writeFile(CREDENTIALS_PATH, JSON.stringify(credentials, null, 2))\n}\n\nexport async function clearCredentials(): Promise<void> {\n try {\n await fs.unlink(CREDENTIALS_PATH)\n } catch {\n // Ignore if file doesn't exist\n }\n}\n","import os from 'node:os'\nimport path from 'node:path'\n\nexport const CONFIG_DIR = path.join(os.homedir(), '.urur')\nexport const CREDENTIALS_PATH = path.join(CONFIG_DIR, 'credentials.json')\n\n// tsup の define でビルド時に埋め込まれる\ndeclare const __SUPABASE_URL__: string\ndeclare const __SUPABASE_ANON_KEY__: string\ndeclare const __WEB_URL__: string\n\nexport const SUPABASE_URL: string = __SUPABASE_URL__\nexport const SUPABASE_ANON_KEY: string = __SUPABASE_ANON_KEY__\nexport const WEB_URL: string = __WEB_URL__\n","import { SUPABASE_ANON_KEY, SUPABASE_URL } from './config.js'\n\nexport interface DeviceCodeResponse {\n userCode: string\n verificationUri: string\n expiresIn: number\n interval: number\n deviceCode: string\n}\n\nexport interface DeviceFlowSession {\n accessToken: string\n refreshToken: string\n expiresAt: number\n userName: string\n}\n\nexport type PollTokenResponse =\n | { status: 'pending' }\n | { status: 'slow_down' }\n | { status: 'expired' }\n | { status: 'success'; session: DeviceFlowSession }\n | { status: 'error'; message: string }\n\nconst EDGE_FUNCTION_URL = `${SUPABASE_URL}/functions/v1/cli-auth`\n\nfunction buildHeaders(): Record<string, string> {\n return {\n 'Content-Type': 'application/json',\n Authorization: `Bearer ${SUPABASE_ANON_KEY}`,\n }\n}\n\nexport async function requestDeviceCode(): Promise<DeviceCodeResponse> {\n const res = await fetch(EDGE_FUNCTION_URL, {\n method: 'POST',\n headers: buildHeaders(),\n body: JSON.stringify({ action: 'device-code' }),\n })\n\n if (!res.ok) {\n throw new Error(`Edge Function エラー: ${res.status}`)\n }\n\n return res.json()\n}\n\nexport async function pollToken(\n deviceCode: string,\n): Promise<PollTokenResponse> {\n const res = await fetch(EDGE_FUNCTION_URL, {\n method: 'POST',\n headers: buildHeaders(),\n body: JSON.stringify({ action: 'poll-token', deviceCode }),\n })\n\n if (!res.ok) {\n throw new Error(`Edge Function エラー: ${res.status}`)\n }\n\n return res.json()\n}\n","import {\n type DeviceCodeResponse,\n type DeviceFlowSession,\n pollToken,\n requestDeviceCode,\n} from './edgeFunctionApi.js'\n\nexport type { DeviceCodeResponse, DeviceFlowSession }\n\nexport async function requestCode(): Promise<DeviceCodeResponse> {\n return requestDeviceCode()\n}\n\nexport async function pollForSession(\n deviceCode: string,\n interval: number,\n expiresIn: number,\n): Promise<DeviceFlowSession> {\n let currentInterval = interval\n let elapsed = 0\n\n while (elapsed < expiresIn) {\n await new Promise((resolve) => setTimeout(resolve, currentInterval * 1000))\n elapsed += currentInterval\n\n if (elapsed >= expiresIn) {\n break\n }\n\n const response = await pollToken(deviceCode)\n\n switch (response.status) {\n case 'success':\n return response.session\n case 'pending':\n continue\n case 'slow_down':\n currentInterval += 5\n continue\n case 'expired':\n throw new Error(\n '認証の有効期限が切れました。再度 urur login を実行してください。',\n )\n case 'error':\n throw new Error(response.message)\n }\n }\n\n throw new Error(\n '認証がタイムアウトしました。再度 urur login を実行してください。',\n )\n}\n","import { createClient } from '@supabase/supabase-js'\nimport { SUPABASE_ANON_KEY, SUPABASE_URL } from './config.js'\n\nfunction createMemoryStorage() {\n const store = new Map<string, string>()\n return {\n getItem(key: string): string | null {\n return store.get(key) ?? null\n },\n setItem(key: string, value: string): void {\n store.set(key, value)\n },\n removeItem(key: string): void {\n store.delete(key)\n },\n }\n}\n\nexport function getSupabaseClient() {\n return createClient(SUPABASE_URL, SUPABASE_ANON_KEY, {\n auth: {\n storage: createMemoryStorage(),\n autoRefreshToken: false,\n detectSessionInUrl: false,\n },\n })\n}\n","import { getSupabaseClient } from './supabase.js'\n\nexport interface MagicLinkSession {\n accessToken: string\n refreshToken: string\n expiresAt: number\n email: string\n}\n\nexport async function sendOtp(email: string): Promise<void> {\n const supabase = getSupabaseClient()\n const { error } = await supabase.auth.signInWithOtp({ email })\n\n if (error) {\n throw new Error(error.message)\n }\n}\n\nexport async function verifyOtp(\n email: string,\n token: string,\n): Promise<MagicLinkSession> {\n const supabase = getSupabaseClient()\n const { data, error } = await supabase.auth.verifyOtp({\n email,\n token,\n type: 'email',\n })\n\n if (error) {\n throw new Error(error.message)\n }\n\n if (!data.session) {\n throw new Error('セッションが取得できませんでした')\n }\n\n return {\n accessToken: data.session.access_token,\n refreshToken: data.session.refresh_token,\n expiresAt: data.session.expires_at ?? 0,\n email: data.session.user?.email ?? email,\n }\n}\n","import pc from 'picocolors'\nimport { clearCredentials, loadCredentials } from '../lib/auth.js'\n\nexport async function logout(): Promise<void> {\n const credentials = await loadCredentials()\n if (!credentials) {\n console.log(pc.yellow('ログインしていません。'))\n return\n }\n\n await clearCredentials()\n console.log(pc.green('ログアウトしました。'))\n}\n","import { confirm, input } from '@inquirer/prompts'\nimport ora from 'ora'\nimport pc from 'picocolors'\nimport { loadCredentials } from '../lib/auth.js'\nimport { getSupabaseClient } from '../lib/supabase.js'\nimport type { ServiceFormData } from '../lib/validation.js'\nimport {\n validateAll,\n validateDescription,\n validateLogoUrl,\n validateName,\n validateTagline,\n validateUrl,\n} from '../lib/validation.js'\n\ninterface SubmitOptions {\n name?: string\n url?: string\n tagline?: string\n description?: string\n logoUrl?: string\n interactive?: boolean\n}\n\nfunction shouldUseInteractive(options: SubmitOptions): boolean {\n return options.interactive === true || !options.name || !options.url\n}\n\nasync function promptServiceData(): Promise<ServiceFormData> {\n const name = await input({\n message: 'プロダクト名(必須):',\n validate: (value) => validateName(value) ?? true,\n })\n\n const url = await input({\n message: 'プロダクトURL(必須):',\n validate: (value) => validateUrl(value) ?? true,\n })\n\n const tagline = await input({\n message: 'タグライン(省略可):',\n validate: (value) => {\n if (!value) return true\n return validateTagline(value) ?? true\n },\n })\n\n const description = await input({\n message: '説明(省略可):',\n validate: (value) => {\n if (!value) return true\n return validateDescription(value) ?? true\n },\n })\n\n const logo_url = await input({\n message: 'ロゴURL(省略可):',\n validate: (value) => {\n if (!value) return true\n return validateLogoUrl(value) ?? true\n },\n })\n\n return {\n name,\n url,\n tagline: tagline || undefined,\n description: description || undefined,\n logo_url: logo_url || undefined,\n }\n}\n\nfunction displaySummary(data: ServiceFormData): void {\n console.log(pc.bold('\\n投稿内容:'))\n console.log(` プロダクト名: ${data.name}`)\n console.log(` URL: ${data.url}`)\n if (data.tagline) console.log(` タグライン: ${data.tagline}`)\n if (data.description) console.log(` 説明: ${data.description}`)\n if (data.logo_url) console.log(` ロゴURL: ${data.logo_url}`)\n console.log('')\n}\n\nfunction displayErrors(errors: Record<string, string>): void {\n console.log(pc.red('\\nバリデーションエラー:'))\n for (const [, message] of Object.entries(errors)) {\n console.log(pc.red(` - ${message}`))\n }\n}\n\nexport async function submit(options: SubmitOptions): Promise<void> {\n // 1. Authentication check\n const credentials = await loadCredentials()\n if (!credentials) {\n console.log(pc.red('ログインが必要です。`urur login` を実行してください。'))\n process.exitCode = 1\n return\n }\n\n const supabase = getSupabaseClient()\n await supabase.auth.setSession({\n access_token: credentials.access_token,\n refresh_token: credentials.refresh_token,\n })\n\n const { data: userData, error: userError } = await supabase.auth.getUser()\n if (userError || !userData.user) {\n console.log(\n pc.red(\n `認証エラー: ${userError?.message ?? 'ユーザー情報を取得できませんでした'}。\\`urur login\\` で再ログインしてください。`,\n ),\n )\n process.exitCode = 1\n return\n }\n\n // 2. Data collection\n let formData: ServiceFormData\n\n if (shouldUseInteractive(options)) {\n formData = await promptServiceData()\n } else {\n // Build form data from CLI options\n formData = {\n name: options.name as string,\n url: options.url as string,\n tagline: options.tagline,\n description: options.description,\n logo_url: options.logoUrl,\n }\n\n // 3. Validation (CLI mode only)\n const result = validateAll(formData)\n if (!result.valid) {\n displayErrors(result.errors)\n process.exitCode = 1\n return\n }\n }\n\n // 4. Summary and confirmation\n displaySummary(formData)\n\n const confirmed = await confirm({\n message: '投稿しますか?',\n default: true,\n })\n\n if (!confirmed) {\n console.log(pc.yellow('投稿をキャンセルしました。'))\n return\n }\n\n // 5. Submit to Supabase\n const spinner = ora('投稿中...').start()\n\n const { error: insertError } = await supabase\n .from('services')\n .insert({\n user_id: userData.user.id,\n name: formData.name,\n url: formData.url,\n tagline: formData.tagline ?? null,\n description: formData.description ?? null,\n logo_url: formData.logo_url ?? null,\n source: 'cli' as const,\n })\n .select()\n .single()\n\n if (insertError) {\n spinner.fail()\n console.log(pc.red(`投稿エラー: ${insertError.message}`))\n process.exitCode = 1\n return\n }\n\n spinner.succeed()\n console.log(pc.green(`「${formData.name}」を投稿しました!`))\n}\n","/**\n * Service submission form validation\n * Shared between client and server\n */\n\nexport interface ServiceFormData {\n name: string\n url: string\n category_id?: string\n tagline?: string\n description?: string\n logo_url?: string\n}\n\nexport interface ValidationResult {\n valid: boolean\n errors: Record<string, string>\n}\n\n// Error messages in Japanese\nconst ERROR_MESSAGES = {\n name: {\n required: 'プロダクト名は必須です',\n maxLength: 'プロダクト名は100文字以内で入力してください',\n },\n url: {\n required: 'URLは必須です',\n invalid: '有効なURLを入力してください',\n },\n tagline: {\n maxLength: 'タグラインは200文字以内で入力してください',\n },\n description: {\n maxLength: '説明は2000文字以内で入力してください',\n },\n logo_url: {\n invalid: '有効なURLを入力してください',\n },\n} as const\n\n// Validation rules\nconst VALIDATION_RULES = {\n name: { required: true, maxLength: 100 },\n url: { required: true },\n tagline: { maxLength: 200 },\n description: { maxLength: 2000 },\n} as const\n\nfunction isValidUrl(value: string): boolean {\n try {\n new URL(value)\n return true\n } catch {\n return false\n }\n}\n\nexport function validateName(value: string): string | null {\n const trimmed = value.trim()\n if (!trimmed) {\n return ERROR_MESSAGES.name.required\n }\n if (trimmed.length > VALIDATION_RULES.name.maxLength) {\n return ERROR_MESSAGES.name.maxLength\n }\n return null\n}\n\nexport function validateUrl(value: string): string | null {\n const trimmed = value.trim()\n if (!trimmed) {\n return ERROR_MESSAGES.url.required\n }\n if (!isValidUrl(trimmed)) {\n return ERROR_MESSAGES.url.invalid\n }\n return null\n}\n\nexport function validateTagline(value: string | undefined): string | null {\n if (!value) {\n return null\n }\n if (value.length > VALIDATION_RULES.tagline.maxLength) {\n return ERROR_MESSAGES.tagline.maxLength\n }\n return null\n}\n\nexport function validateDescription(value: string | undefined): string | null {\n if (!value) {\n return null\n }\n if (value.length > VALIDATION_RULES.description.maxLength) {\n return ERROR_MESSAGES.description.maxLength\n }\n return null\n}\n\nexport function validateLogoUrl(value: string | undefined): string | null {\n if (!value) {\n return null\n }\n if (!isValidUrl(value)) {\n return ERROR_MESSAGES.logo_url.invalid\n }\n return null\n}\n\nexport function validateAll(data: Partial<ServiceFormData>): ValidationResult {\n const errors: Record<string, string> = {}\n\n const nameError = validateName(data.name ?? '')\n if (nameError) {\n errors.name = nameError\n }\n\n const urlError = validateUrl(data.url ?? '')\n if (urlError) {\n errors.url = urlError\n }\n\n const taglineError = validateTagline(data.tagline)\n if (taglineError) {\n errors.tagline = taglineError\n }\n\n const descriptionError = validateDescription(data.description)\n if (descriptionError) {\n errors.description = descriptionError\n }\n\n const logoUrlError = validateLogoUrl(data.logo_url)\n if (logoUrlError) {\n errors.logo_url = logoUrlError\n }\n\n return {\n valid: Object.keys(errors).length === 0,\n errors,\n }\n}\n","import pc from 'picocolors'\n\nexport async function whoami(): Promise<void> {\n console.log(\n pc.yellow('⚠ urur whoami は未実装です。Kiro specで実装を進めてください。'),\n )\n}\n","import { createProgram } from './program.js'\n\nconst program = createProgram()\nprogram.parse()\n"],"mappings":"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","names":["pc","pc","confirm","input","ora","pc","input","pc","confirm","ora","pc","program"]}
|
package/package.json
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "urur",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "CLI for urur.dev - 個人開発プロダクトディレクトリ",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "https://github.com/shampagne/urur-cli.git"
|
|
9
|
+
},
|
|
10
|
+
"homepage": "https://urur.dev",
|
|
11
|
+
"bugs": {
|
|
12
|
+
"url": "https://github.com/shampagne/urur-cli/issues"
|
|
13
|
+
},
|
|
14
|
+
"keywords": [
|
|
15
|
+
"cli",
|
|
16
|
+
"urur",
|
|
17
|
+
"product",
|
|
18
|
+
"indie-dev",
|
|
19
|
+
"indie-hacker"
|
|
20
|
+
],
|
|
21
|
+
"type": "module",
|
|
22
|
+
"bin": {
|
|
23
|
+
"urur": "./dist/index.js"
|
|
24
|
+
},
|
|
25
|
+
"files": [
|
|
26
|
+
"dist"
|
|
27
|
+
],
|
|
28
|
+
"scripts": {
|
|
29
|
+
"dev": "tsup --watch",
|
|
30
|
+
"build": "tsup",
|
|
31
|
+
"test": "vitest run",
|
|
32
|
+
"test:watch": "vitest",
|
|
33
|
+
"test:coverage": "vitest run --coverage",
|
|
34
|
+
"lint": "biome lint src/ tests/",
|
|
35
|
+
"format": "biome format --write src/ tests/",
|
|
36
|
+
"check": "biome check src/ tests/",
|
|
37
|
+
"typecheck": "tsc --noEmit",
|
|
38
|
+
"prepublishOnly": "npm run build"
|
|
39
|
+
},
|
|
40
|
+
"dependencies": {
|
|
41
|
+
"@inquirer/prompts": "^7.5.0",
|
|
42
|
+
"@supabase/supabase-js": "^2.49.1",
|
|
43
|
+
"commander": "^13.1.0",
|
|
44
|
+
"open": "^10.1.0",
|
|
45
|
+
"ora": "^8.2.0",
|
|
46
|
+
"picocolors": "^1.1.1"
|
|
47
|
+
},
|
|
48
|
+
"devDependencies": {
|
|
49
|
+
"@biomejs/biome": "2.0.6",
|
|
50
|
+
"@types/node": "^22.15.0",
|
|
51
|
+
"@vitest/coverage-v8": "^4.0.18",
|
|
52
|
+
"lefthook": "^2.1.0",
|
|
53
|
+
"tsup": "^8.4.0",
|
|
54
|
+
"typescript": "^5.8.0",
|
|
55
|
+
"vitest": "^4.0.18"
|
|
56
|
+
},
|
|
57
|
+
"engines": {
|
|
58
|
+
"node": ">=18.0.0"
|
|
59
|
+
}
|
|
60
|
+
}
|