@o-t-p-y/cli 0.3.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/LICENSE +21 -0
- package/README.md +42 -0
- package/dist/detector.d.ts +11 -0
- package/dist/detector.js +116 -0
- package/dist/detector.js.map +1 -0
- package/dist/env.d.ts +2 -0
- package/dist/env.js +29 -0
- package/dist/env.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +243 -0
- package/dist/index.js.map +1 -0
- package/dist/templates.d.ts +12 -0
- package/dist/templates.js +517 -0
- package/dist/templates.js.map +1 -0
- package/package.json +47 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 OTPy
|
|
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,42 @@
|
|
|
1
|
+
# otpy-cli
|
|
2
|
+
|
|
3
|
+
The official CLI and automatic integration wizard for [OTPy.ir](https://otpy.ir).
|
|
4
|
+
|
|
5
|
+
## Quick Start
|
|
6
|
+
|
|
7
|
+
Initialize OTP authentication in your existing project with one command:
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npx @o-t-p-y/cli init
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
The CLI will:
|
|
14
|
+
1. Detect your framework (Next.js App/Pages router, SvelteKit, Express, Python FastAPI/Django, Go, PHP Laravel).
|
|
15
|
+
2. Ask for or detect your `OTPY_API_KEY` and safely update your `.env` or `.env.local`.
|
|
16
|
+
3. Generate ready-to-run client and route handler files.
|
|
17
|
+
|
|
18
|
+
## Other Commands
|
|
19
|
+
|
|
20
|
+
### Send a Test OTP
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
npx @o-t-p-y/cli test 09123456789
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
### View Daily Quota Usage
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
npx @o-t-p-y/cli usage
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
### AI Integration Mode
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
npx @o-t-p-y/cli init --ai
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
Outputs prompt instructions for AI coding assistants (Cursor, Windsurf, Claude Code, GitHub Copilot).
|
|
39
|
+
|
|
40
|
+
## License
|
|
41
|
+
|
|
42
|
+
MIT © [OTPy.ir](https://otpy.ir)
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export type Framework = "next-app" | "next-pages" | "sveltekit" | "express" | "node-generic" | "python-fastapi" | "python-django" | "python-generic" | "php-laravel" | "php-generic" | "go" | "unknown";
|
|
2
|
+
export interface ProjectInfo {
|
|
3
|
+
framework: Framework;
|
|
4
|
+
isTypeScript: boolean;
|
|
5
|
+
hasSrcDir: boolean;
|
|
6
|
+
hasEnvFile: boolean;
|
|
7
|
+
envFilePath: string;
|
|
8
|
+
}
|
|
9
|
+
export type NextPagesRoot = "pages" | "src/pages";
|
|
10
|
+
export declare function detectNextPagesRoot(cwd?: string): NextPagesRoot;
|
|
11
|
+
export declare function detectProject(cwd?: string): ProjectInfo;
|
package/dist/detector.js
ADDED
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
// Next.js ignores src/pages when a root pages dir exists — root wins.
|
|
4
|
+
export function detectNextPagesRoot(cwd = process.cwd()) {
|
|
5
|
+
if (existsSync(join(cwd, "pages")))
|
|
6
|
+
return "pages";
|
|
7
|
+
if (existsSync(join(cwd, "src", "pages")))
|
|
8
|
+
return "src/pages";
|
|
9
|
+
return "pages";
|
|
10
|
+
}
|
|
11
|
+
export function detectProject(cwd = process.cwd()) {
|
|
12
|
+
const hasPkgJson = existsSync(join(cwd, "package.json"));
|
|
13
|
+
const hasTsConfig = existsSync(join(cwd, "tsconfig.json"));
|
|
14
|
+
const hasSrcDir = existsSync(join(cwd, "src"));
|
|
15
|
+
const envLocal = existsSync(join(cwd, ".env.local"));
|
|
16
|
+
const envMain = existsSync(join(cwd, ".env"));
|
|
17
|
+
const hasEnvFile = envLocal || envMain;
|
|
18
|
+
const envFilePath = envLocal ? join(cwd, ".env.local") : join(cwd, ".env");
|
|
19
|
+
if (hasPkgJson) {
|
|
20
|
+
try {
|
|
21
|
+
const pkg = JSON.parse(readFileSync(join(cwd, "package.json"), "utf8"));
|
|
22
|
+
const allDeps = {
|
|
23
|
+
...pkg.dependencies,
|
|
24
|
+
...pkg.devDependencies,
|
|
25
|
+
};
|
|
26
|
+
if (allDeps["next"]) {
|
|
27
|
+
const hasAppDir = existsSync(join(cwd, "app")) || existsSync(join(cwd, "src", "app"));
|
|
28
|
+
return {
|
|
29
|
+
framework: hasAppDir ? "next-app" : "next-pages",
|
|
30
|
+
isTypeScript: hasTsConfig,
|
|
31
|
+
hasSrcDir,
|
|
32
|
+
hasEnvFile,
|
|
33
|
+
envFilePath,
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
if (allDeps["@sveltejs/kit"]) {
|
|
37
|
+
return {
|
|
38
|
+
framework: "sveltekit",
|
|
39
|
+
isTypeScript: hasTsConfig,
|
|
40
|
+
hasSrcDir,
|
|
41
|
+
hasEnvFile,
|
|
42
|
+
envFilePath,
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
if (allDeps["express"] || allDeps["fastify"] || allDeps["koa"] || allDeps["hono"]) {
|
|
46
|
+
return {
|
|
47
|
+
framework: "express",
|
|
48
|
+
isTypeScript: hasTsConfig,
|
|
49
|
+
hasSrcDir,
|
|
50
|
+
hasEnvFile,
|
|
51
|
+
envFilePath,
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
return {
|
|
55
|
+
framework: "node-generic",
|
|
56
|
+
isTypeScript: hasTsConfig,
|
|
57
|
+
hasSrcDir,
|
|
58
|
+
hasEnvFile,
|
|
59
|
+
envFilePath,
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
catch {
|
|
63
|
+
// Fall through
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
// Check Python
|
|
67
|
+
if (existsSync(join(cwd, "pyproject.toml")) ||
|
|
68
|
+
existsSync(join(cwd, "requirements.txt")) ||
|
|
69
|
+
existsSync(join(cwd, "Pipfile"))) {
|
|
70
|
+
if (existsSync(join(cwd, "manage.py"))) {
|
|
71
|
+
return {
|
|
72
|
+
framework: "python-django",
|
|
73
|
+
isTypeScript: false,
|
|
74
|
+
hasSrcDir,
|
|
75
|
+
hasEnvFile,
|
|
76
|
+
envFilePath,
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
return {
|
|
80
|
+
framework: "python-fastapi",
|
|
81
|
+
isTypeScript: false,
|
|
82
|
+
hasSrcDir,
|
|
83
|
+
hasEnvFile,
|
|
84
|
+
envFilePath,
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
// Check PHP
|
|
88
|
+
if (existsSync(join(cwd, "composer.json"))) {
|
|
89
|
+
const isLaravel = existsSync(join(cwd, "artisan"));
|
|
90
|
+
return {
|
|
91
|
+
framework: isLaravel ? "php-laravel" : "php-generic",
|
|
92
|
+
isTypeScript: false,
|
|
93
|
+
hasSrcDir,
|
|
94
|
+
hasEnvFile,
|
|
95
|
+
envFilePath,
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
// Check Go
|
|
99
|
+
if (existsSync(join(cwd, "go.mod"))) {
|
|
100
|
+
return {
|
|
101
|
+
framework: "go",
|
|
102
|
+
isTypeScript: false,
|
|
103
|
+
hasSrcDir,
|
|
104
|
+
hasEnvFile,
|
|
105
|
+
envFilePath,
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
return {
|
|
109
|
+
framework: "unknown",
|
|
110
|
+
isTypeScript: hasTsConfig,
|
|
111
|
+
hasSrcDir,
|
|
112
|
+
hasEnvFile,
|
|
113
|
+
envFilePath,
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
//# sourceMappingURL=detector.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"detector.js","sourceRoot":"","sources":["../src/detector.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACnD,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AA0BjC,sEAAsE;AACtE,MAAM,UAAU,mBAAmB,CAAC,MAAc,OAAO,CAAC,GAAG,EAAE;IAC7D,IAAI,UAAU,CAAC,IAAI,CAAC,GAAG,EAAE,OAAO,CAAC,CAAC;QAAE,OAAO,OAAO,CAAC;IACnD,IAAI,UAAU,CAAC,IAAI,CAAC,GAAG,EAAE,KAAK,EAAE,OAAO,CAAC,CAAC;QAAE,OAAO,WAAW,CAAC;IAC9D,OAAO,OAAO,CAAC;AACjB,CAAC;AAED,MAAM,UAAU,aAAa,CAAC,MAAc,OAAO,CAAC,GAAG,EAAE;IACvD,MAAM,UAAU,GAAG,UAAU,CAAC,IAAI,CAAC,GAAG,EAAE,cAAc,CAAC,CAAC,CAAC;IACzD,MAAM,WAAW,GAAG,UAAU,CAAC,IAAI,CAAC,GAAG,EAAE,eAAe,CAAC,CAAC,CAAC;IAC3D,MAAM,SAAS,GAAG,UAAU,CAAC,IAAI,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC,CAAC;IAE/C,MAAM,QAAQ,GAAG,UAAU,CAAC,IAAI,CAAC,GAAG,EAAE,YAAY,CAAC,CAAC,CAAC;IACrD,MAAM,OAAO,GAAG,UAAU,CAAC,IAAI,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC,CAAC;IAC9C,MAAM,UAAU,GAAG,QAAQ,IAAI,OAAO,CAAC;IACvC,MAAM,WAAW,GAAG,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,EAAE,YAAY,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC;IAE3E,IAAI,UAAU,EAAE,CAAC;QACf,IAAI,CAAC;YACH,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,IAAI,CAAC,GAAG,EAAE,cAAc,CAAC,EAAE,MAAM,CAAC,CAAC,CAAC;YACxE,MAAM,OAAO,GAAG;gBACd,GAAG,GAAG,CAAC,YAAY;gBACnB,GAAG,GAAG,CAAC,eAAe;aACvB,CAAC;YAEF,IAAI,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC;gBACpB,MAAM,SAAS,GAAG,UAAU,CAAC,IAAI,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC,IAAI,UAAU,CAAC,IAAI,CAAC,GAAG,EAAE,KAAK,EAAE,KAAK,CAAC,CAAC,CAAC;gBACtF,OAAO;oBACL,SAAS,EAAE,SAAS,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,YAAY;oBAChD,YAAY,EAAE,WAAW;oBACzB,SAAS;oBACT,UAAU;oBACV,WAAW;iBACZ,CAAC;YACJ,CAAC;YAED,IAAI,OAAO,CAAC,eAAe,CAAC,EAAE,CAAC;gBAC7B,OAAO;oBACL,SAAS,EAAE,WAAW;oBACtB,YAAY,EAAE,WAAW;oBACzB,SAAS;oBACT,UAAU;oBACV,WAAW;iBACZ,CAAC;YACJ,CAAC;YAED,IAAI,OAAO,CAAC,SAAS,CAAC,IAAI,OAAO,CAAC,SAAS,CAAC,IAAI,OAAO,CAAC,KAAK,CAAC,IAAI,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC;gBAClF,OAAO;oBACL,SAAS,EAAE,SAAS;oBACpB,YAAY,EAAE,WAAW;oBACzB,SAAS;oBACT,UAAU;oBACV,WAAW;iBACZ,CAAC;YACJ,CAAC;YAED,OAAO;gBACL,SAAS,EAAE,cAAc;gBACzB,YAAY,EAAE,WAAW;gBACzB,SAAS;gBACT,UAAU;gBACV,WAAW;aACZ,CAAC;QACJ,CAAC;QAAC,MAAM,CAAC;YACP,eAAe;QACjB,CAAC;IACH,CAAC;IAED,eAAe;IACf,IACE,UAAU,CAAC,IAAI,CAAC,GAAG,EAAE,gBAAgB,CAAC,CAAC;QACvC,UAAU,CAAC,IAAI,CAAC,GAAG,EAAE,kBAAkB,CAAC,CAAC;QACzC,UAAU,CAAC,IAAI,CAAC,GAAG,EAAE,SAAS,CAAC,CAAC,EAChC,CAAC;QACD,IAAI,UAAU,CAAC,IAAI,CAAC,GAAG,EAAE,WAAW,CAAC,CAAC,EAAE,CAAC;YACvC,OAAO;gBACL,SAAS,EAAE,eAAe;gBAC1B,YAAY,EAAE,KAAK;gBACnB,SAAS;gBACT,UAAU;gBACV,WAAW;aACZ,CAAC;QACJ,CAAC;QACD,OAAO;YACL,SAAS,EAAE,gBAAgB;YAC3B,YAAY,EAAE,KAAK;YACnB,SAAS;YACT,UAAU;YACV,WAAW;SACZ,CAAC;IACJ,CAAC;IAED,YAAY;IACZ,IAAI,UAAU,CAAC,IAAI,CAAC,GAAG,EAAE,eAAe,CAAC,CAAC,EAAE,CAAC;QAC3C,MAAM,SAAS,GAAG,UAAU,CAAC,IAAI,CAAC,GAAG,EAAE,SAAS,CAAC,CAAC,CAAC;QACnD,OAAO;YACL,SAAS,EAAE,SAAS,CAAC,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC,aAAa;YACpD,YAAY,EAAE,KAAK;YACnB,SAAS;YACT,UAAU;YACV,WAAW;SACZ,CAAC;IACJ,CAAC;IAED,WAAW;IACX,IAAI,UAAU,CAAC,IAAI,CAAC,GAAG,EAAE,QAAQ,CAAC,CAAC,EAAE,CAAC;QACpC,OAAO;YACL,SAAS,EAAE,IAAI;YACf,YAAY,EAAE,KAAK;YACnB,SAAS;YACT,UAAU;YACV,WAAW;SACZ,CAAC;IACJ,CAAC;IAED,OAAO;QACL,SAAS,EAAE,SAAS;QACpB,YAAY,EAAE,WAAW;QACzB,SAAS;QACT,UAAU;QACV,WAAW;KACZ,CAAC;AACJ,CAAC"}
|
package/dist/env.d.ts
ADDED
package/dist/env.js
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { existsSync, readFileSync, writeFileSync } from "node:fs";
|
|
2
|
+
export function getExistingEnvKey(filePath, keyName = "OTPY_API_KEY") {
|
|
3
|
+
if (!existsSync(filePath))
|
|
4
|
+
return null;
|
|
5
|
+
try {
|
|
6
|
+
const content = readFileSync(filePath, "utf8");
|
|
7
|
+
const match = new RegExp(`^${keyName}=(.*)$`, "m").exec(content);
|
|
8
|
+
return match?.[1]?.trim() ?? null;
|
|
9
|
+
}
|
|
10
|
+
catch {
|
|
11
|
+
return null;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
export function appendOrUpdateEnvKey(filePath, keyName, keyValue) {
|
|
15
|
+
let content = "";
|
|
16
|
+
if (existsSync(filePath)) {
|
|
17
|
+
content = readFileSync(filePath, "utf8");
|
|
18
|
+
}
|
|
19
|
+
const regex = new RegExp(`^${keyName}=.*$`, "m");
|
|
20
|
+
if (regex.test(content)) {
|
|
21
|
+
content = content.replace(regex, `${keyName}=${keyValue}`);
|
|
22
|
+
}
|
|
23
|
+
else {
|
|
24
|
+
const endsWithNewline = content.length === 0 || content.endsWith("\n");
|
|
25
|
+
content = `${content}${endsWithNewline ? "" : "\n"}${keyName}=${keyValue}\n`;
|
|
26
|
+
}
|
|
27
|
+
writeFileSync(filePath, content, "utf8");
|
|
28
|
+
}
|
|
29
|
+
//# sourceMappingURL=env.js.map
|
package/dist/env.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"env.js","sourceRoot":"","sources":["../src/env.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAElE,MAAM,UAAU,iBAAiB,CAAC,QAAgB,EAAE,UAAkB,cAAc;IAClF,IAAI,CAAC,UAAU,CAAC,QAAQ,CAAC;QAAE,OAAO,IAAI,CAAC;IACvC,IAAI,CAAC;QACH,MAAM,OAAO,GAAG,YAAY,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;QAC/C,MAAM,KAAK,GAAG,IAAI,MAAM,CAAC,IAAI,OAAO,QAAQ,EAAE,GAAG,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QACjE,OAAO,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,IAAI,IAAI,CAAC;IACpC,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC;AAED,MAAM,UAAU,oBAAoB,CAAC,QAAgB,EAAE,OAAe,EAAE,QAAgB;IACtF,IAAI,OAAO,GAAG,EAAE,CAAC;IACjB,IAAI,UAAU,CAAC,QAAQ,CAAC,EAAE,CAAC;QACzB,OAAO,GAAG,YAAY,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;IAC3C,CAAC;IAED,MAAM,KAAK,GAAG,IAAI,MAAM,CAAC,IAAI,OAAO,MAAM,EAAE,GAAG,CAAC,CAAC;IACjD,IAAI,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC;QACxB,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,KAAK,EAAE,GAAG,OAAO,IAAI,QAAQ,EAAE,CAAC,CAAC;IAC7D,CAAC;SAAM,CAAC;QACN,MAAM,eAAe,GAAG,OAAO,CAAC,MAAM,KAAK,CAAC,IAAI,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC;QACvE,OAAO,GAAG,GAAG,OAAO,GAAG,eAAe,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,GAAG,OAAO,IAAI,QAAQ,IAAI,CAAC;IAC/E,CAAC;IAED,aAAa,CAAC,QAAQ,EAAE,OAAO,EAAE,MAAM,CAAC,CAAC;AAC3C,CAAC"}
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { createInterface } from "node:readline";
|
|
3
|
+
import { dirname, join } from "node:path";
|
|
4
|
+
import { existsSync, mkdirSync, writeFileSync } from "node:fs";
|
|
5
|
+
import packageMetadata from "../package.json" with { type: "json" };
|
|
6
|
+
import { detectNextPagesRoot, detectProject } from "./detector.js";
|
|
7
|
+
import { appendOrUpdateEnvKey, getExistingEnvKey } from "./env.js";
|
|
8
|
+
import { generateExpressTemplates, generateGoTemplates, generateNextAppTemplates, generateNextPagesTemplates, generatePhpLaravelTemplates, generatePythonFastApiTemplates, generateSvelteKitTemplates, phpLaravelRoutesSnippet, } from "./templates.js";
|
|
9
|
+
const args = process.argv.slice(2);
|
|
10
|
+
const command = args[0] || "init";
|
|
11
|
+
const placeholderApiKey = "otpy_test_key_replace_with_yours";
|
|
12
|
+
function printBanner() {
|
|
13
|
+
console.log(`
|
|
14
|
+
┌────────────────────────────────────────────────────────┐
|
|
15
|
+
│ OTPy.ir — سامانه هوشمند ارسال پیامک کد ورود (OTP) │
|
|
16
|
+
│ سریع، اقتصادی، بدون خط اختصاصی و قرارداد │
|
|
17
|
+
└────────────────────────────────────────────────────────┘
|
|
18
|
+
`);
|
|
19
|
+
}
|
|
20
|
+
function prompt(question) {
|
|
21
|
+
const rl = createInterface({
|
|
22
|
+
input: process.stdin,
|
|
23
|
+
output: process.stdout,
|
|
24
|
+
});
|
|
25
|
+
return new Promise((resolve) => {
|
|
26
|
+
rl.question(question, (answer) => {
|
|
27
|
+
rl.close();
|
|
28
|
+
resolve(answer.trim());
|
|
29
|
+
});
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
async function runInit() {
|
|
33
|
+
printBanner();
|
|
34
|
+
const cwd = process.cwd();
|
|
35
|
+
const info = detectProject(cwd);
|
|
36
|
+
console.log(`🔍 بررسی پروژه...`);
|
|
37
|
+
console.log(` فریمورک شناسایی شده: ${info.framework}`);
|
|
38
|
+
console.log(` پشتیبانی از تایپاسکریپت: ${info.isTypeScript ? "بله" : "خیر"}`);
|
|
39
|
+
console.log(` فایل محیطی (.env): ${info.envFilePath}\n`);
|
|
40
|
+
let apiKey = getExistingEnvKey(info.envFilePath);
|
|
41
|
+
const cliKeyArgIndex = args.indexOf("--api-key");
|
|
42
|
+
if (cliKeyArgIndex !== -1 && args[cliKeyArgIndex + 1]) {
|
|
43
|
+
apiKey = args[cliKeyArgIndex + 1] ?? null;
|
|
44
|
+
}
|
|
45
|
+
if (!apiKey) {
|
|
46
|
+
console.log(`💡 کلید API پروژه خود را وارد کنید (یا از داشبورد https://dash.otpy.ir کپی کنید):`);
|
|
47
|
+
apiKey = (await prompt("🔑 کلید API: ")) || null;
|
|
48
|
+
}
|
|
49
|
+
if (apiKey && apiKey !== placeholderApiKey) {
|
|
50
|
+
appendOrUpdateEnvKey(info.envFilePath, "OTPY_API_KEY", apiKey);
|
|
51
|
+
console.log(`\n✔ کلید در فایل ${info.envFilePath} ذخیره شد.`);
|
|
52
|
+
}
|
|
53
|
+
else {
|
|
54
|
+
console.log(`\n💡 برای دریافت کلید API به https://dash.otpy.ir مراجعه کنید.`);
|
|
55
|
+
}
|
|
56
|
+
// Generate templates
|
|
57
|
+
let filesToGenerate = [];
|
|
58
|
+
if (info.framework === "next-app") {
|
|
59
|
+
filesToGenerate = generateNextAppTemplates(info.hasSrcDir, info.isTypeScript);
|
|
60
|
+
}
|
|
61
|
+
else if (info.framework === "next-pages") {
|
|
62
|
+
filesToGenerate = generateNextPagesTemplates(detectNextPagesRoot(cwd) === "src/pages", info.isTypeScript);
|
|
63
|
+
}
|
|
64
|
+
else if (info.framework === "sveltekit") {
|
|
65
|
+
filesToGenerate = generateSvelteKitTemplates();
|
|
66
|
+
}
|
|
67
|
+
else if (info.framework === "express" || info.framework === "node-generic") {
|
|
68
|
+
filesToGenerate = generateExpressTemplates(info.hasSrcDir, info.isTypeScript);
|
|
69
|
+
}
|
|
70
|
+
else if (info.framework === "python-fastapi" || info.framework === "python-django") {
|
|
71
|
+
filesToGenerate = generatePythonFastApiTemplates();
|
|
72
|
+
}
|
|
73
|
+
else if (info.framework === "go") {
|
|
74
|
+
filesToGenerate = generateGoTemplates();
|
|
75
|
+
}
|
|
76
|
+
else if (info.framework === "php-laravel") {
|
|
77
|
+
filesToGenerate = generatePhpLaravelTemplates();
|
|
78
|
+
if (existsSync(join(cwd, "routes/api.php"))) {
|
|
79
|
+
console.log(`\n📝 فایل routes/api.php از قبل موجود است؛ این خطوط را به آن اضافه کنید:`);
|
|
80
|
+
console.log(phpLaravelRoutesSnippet);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
else if (info.framework === "php-generic") {
|
|
84
|
+
filesToGenerate = [];
|
|
85
|
+
console.log(`\n🐘 پروژه PHP شناسایی شد، اما فریمورک پشتیبانیشدهای (Laravel) پیدا نشد.`);
|
|
86
|
+
console.log(` برای ادغام دستی، مستندات را ببینید: https://otpy.ir/docs`);
|
|
87
|
+
console.log(` نمونه ارسال با cURL:`);
|
|
88
|
+
console.log(` curl -X POST https://api.otpy.ir/v1/otp/send \\`);
|
|
89
|
+
console.log(` -H "Authorization: Bearer $OTPY_API_KEY" -H "Content-Type: application/json" \\`);
|
|
90
|
+
console.log(` -d '{"phone":"09123456789"}'`);
|
|
91
|
+
}
|
|
92
|
+
else {
|
|
93
|
+
filesToGenerate = generateNextAppTemplates(info.hasSrcDir, info.isTypeScript);
|
|
94
|
+
}
|
|
95
|
+
if (filesToGenerate.length > 0) {
|
|
96
|
+
console.log(`\n📦 در حال ایجاد فایلهای ادغام و کلاینت:`);
|
|
97
|
+
for (const file of filesToGenerate) {
|
|
98
|
+
const fullPath = join(cwd, file.path);
|
|
99
|
+
const parentDir = dirname(fullPath);
|
|
100
|
+
if (!existsSync(parentDir)) {
|
|
101
|
+
mkdirSync(parentDir, { recursive: true });
|
|
102
|
+
}
|
|
103
|
+
if (!existsSync(fullPath)) {
|
|
104
|
+
writeFileSync(fullPath, file.content, "utf8");
|
|
105
|
+
console.log(` + ایجاد فایل: ${file.path}`);
|
|
106
|
+
}
|
|
107
|
+
else {
|
|
108
|
+
console.log(` ~ فایل موجود بود (رد شد): ${file.path}`);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
if (args.includes("--ai")) {
|
|
113
|
+
console.log(`\n🤖 دستورالعمل هوش مصنوعی برای ابزارهای Cursor / Windsurf / Claude Code:`);
|
|
114
|
+
if (info.framework === "php-laravel" || info.framework === "php-generic") {
|
|
115
|
+
console.log(` - REST API: https://api.otpy.ir`);
|
|
116
|
+
console.log(` - ارسال: POST /v1/otp/send با بدنه {"phone": "09123456789"}`);
|
|
117
|
+
console.log(` - تایید: POST /v1/otp/verify با بدنه {"phone": "09123456789", "code": "123456"} → {verified: boolean}`);
|
|
118
|
+
}
|
|
119
|
+
else {
|
|
120
|
+
console.log(` - کتابخانه: @o-t-p-y/sdk`);
|
|
121
|
+
console.log(` - ارسال: otpy.sendOtp(phone) -> { request_id, ttl_seconds }`);
|
|
122
|
+
console.log(` - تایید: otpy.verifyOtp(phone, code) -> { verified: true }`);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
if (info.framework === "php-generic") {
|
|
126
|
+
console.log(`
|
|
127
|
+
برای راهنمای کامل ادغام دستی به https://otpy.ir/docs مراجعه کنید.
|
|
128
|
+
داشبورد و آمار لحظهای: https://dash.otpy.ir
|
|
129
|
+
`);
|
|
130
|
+
}
|
|
131
|
+
else if (info.framework === "php-laravel") {
|
|
132
|
+
console.log(`
|
|
133
|
+
🎉 تبریک! ادغام با موفقیت انجام شد.
|
|
134
|
+
|
|
135
|
+
مراحل بعدی:
|
|
136
|
+
۱. اجرای سرور محلی: php artisan serve
|
|
137
|
+
۲. تست ارسال: curl -X POST http://localhost:8000/api/auth/otp/send -H "Content-Type: application/json" -d '{"phone":"09123456789"}'
|
|
138
|
+
۳. داشبورد و آمار لحظهای: https://dash.otpy.ir
|
|
139
|
+
`);
|
|
140
|
+
}
|
|
141
|
+
else {
|
|
142
|
+
console.log(`
|
|
143
|
+
🎉 تبریک! ادغام با موفقیت انجام شد.
|
|
144
|
+
|
|
145
|
+
مراحل بعدی:
|
|
146
|
+
۱. برای نصب پکیج: npm install @o-t-p-y/sdk
|
|
147
|
+
۲. برای تست ارسال پیامک: npx @o-t-p-y/cli test 09123456789
|
|
148
|
+
۳. داشبورد و آمار لحظهای: https://dash.otpy.ir
|
|
149
|
+
`);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
async function runTest() {
|
|
153
|
+
const phone = args[1];
|
|
154
|
+
if (!phone || !/^09\d{9}$/.test(phone)) {
|
|
155
|
+
console.log("❌ خطا: شماره موبایل معتبر الزامی است. مثال: npx @o-t-p-y/cli test 09123456789");
|
|
156
|
+
process.exit(1);
|
|
157
|
+
}
|
|
158
|
+
const info = detectProject(process.cwd());
|
|
159
|
+
const apiKey = getExistingEnvKey(info.envFilePath);
|
|
160
|
+
if (!apiKey) {
|
|
161
|
+
console.log("❌ کلید OTPY_API_KEY در فایل .env یافت نشد. ابتدا npx @o-t-p-y/cli init را اجرا کنید.");
|
|
162
|
+
process.exit(1);
|
|
163
|
+
}
|
|
164
|
+
console.log(`🚀 در حال ارسال کد تست به شماره ${phone}...`);
|
|
165
|
+
try {
|
|
166
|
+
const res = await fetch("https://api.otpy.ir/v1/otp/send", {
|
|
167
|
+
method: "POST",
|
|
168
|
+
headers: {
|
|
169
|
+
authorization: `Bearer ${apiKey}`,
|
|
170
|
+
"content-type": "application/json",
|
|
171
|
+
},
|
|
172
|
+
body: JSON.stringify({ phone }),
|
|
173
|
+
});
|
|
174
|
+
const data = (await res.json());
|
|
175
|
+
if (res.ok) {
|
|
176
|
+
console.log(`✔ پیامک ارسال شد!`);
|
|
177
|
+
console.log(` شناسه پیامک: ${data.request_id}`);
|
|
178
|
+
console.log(` نوع مصرف: ${data.free ? "سهمیه رایگان روزانه" : "شارژی"}`);
|
|
179
|
+
}
|
|
180
|
+
else {
|
|
181
|
+
console.log(`❌ خطا در ارسال پیامک: ${data.error || res.statusText}`);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
catch (err) {
|
|
185
|
+
console.log(`❌ خطای شبکه: ${String(err)}`);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
async function runUsage() {
|
|
189
|
+
const info = detectProject(process.cwd());
|
|
190
|
+
const apiKey = getExistingEnvKey(info.envFilePath);
|
|
191
|
+
if (!apiKey) {
|
|
192
|
+
console.log("❌ کلید OTPY_API_KEY در فایل .env یافت نشد.");
|
|
193
|
+
process.exit(1);
|
|
194
|
+
}
|
|
195
|
+
try {
|
|
196
|
+
const res = await fetch("https://api.otpy.ir/v1/usage", {
|
|
197
|
+
method: "GET",
|
|
198
|
+
headers: { authorization: `Bearer ${apiKey}` },
|
|
199
|
+
});
|
|
200
|
+
const data = (await res.json());
|
|
201
|
+
if (res.ok) {
|
|
202
|
+
console.log(`📊 آمار مصرف امروز:`);
|
|
203
|
+
console.log(` رایگان مصرف شده: ${data.free_used_today} از ${data.free_quota_today}`);
|
|
204
|
+
console.log(` پیامکهای شارژی: ${data.paid_today}`);
|
|
205
|
+
console.log(` سقف کل روزانه: ${data.daily_limit ? data.daily_limit : "نامحدود"}`);
|
|
206
|
+
}
|
|
207
|
+
else {
|
|
208
|
+
console.log(`❌ خطا در استعلام آمار.`);
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
catch (err) {
|
|
212
|
+
console.log(`❌ خطای شبکه: ${String(err)}`);
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
if (command === "--version" || command === "-v") {
|
|
216
|
+
console.log(packageMetadata.version);
|
|
217
|
+
process.exit(0);
|
|
218
|
+
}
|
|
219
|
+
if (command === "--help" || command === "-h") {
|
|
220
|
+
console.log(`
|
|
221
|
+
استفاده از دستورات ابزار خط فرمان OTPy:
|
|
222
|
+
npx @o-t-p-y/cli init راهاندازی خودکار پروژه و تولید فایلهای آماده
|
|
223
|
+
npx @o-t-p-y/cli init --ai راهاندازی به همراه راهنمای ایجنتهای هوش مصنوعی
|
|
224
|
+
npx @o-t-p-y/cli test <phone> ارسال پیامک تست به شماره دلخواه
|
|
225
|
+
npx @o-t-p-y/cli usage مشاهده آمار مصرف امروز سهمیه
|
|
226
|
+
npx @o-t-p-y/cli --version نسخه CLI
|
|
227
|
+
`);
|
|
228
|
+
process.exit(0);
|
|
229
|
+
}
|
|
230
|
+
if (command === "init") {
|
|
231
|
+
runInit().catch(console.error);
|
|
232
|
+
}
|
|
233
|
+
else if (command === "test") {
|
|
234
|
+
runTest().catch(console.error);
|
|
235
|
+
}
|
|
236
|
+
else if (command === "usage") {
|
|
237
|
+
runUsage().catch(console.error);
|
|
238
|
+
}
|
|
239
|
+
else {
|
|
240
|
+
console.log(`دستور ناشناخته: ${command}\nبرای راهنما npx @o-t-p-y/cli --help را اجرا کنید.`);
|
|
241
|
+
process.exit(1);
|
|
242
|
+
}
|
|
243
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AACA,OAAO,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAChD,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAC1C,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAC/D,OAAO,eAAe,MAAM,iBAAiB,CAAC,OAAO,IAAI,EAAE,MAAM,EAAE,CAAC;AACpE,OAAO,EAAE,mBAAmB,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AACnE,OAAO,EAAE,oBAAoB,EAAE,iBAAiB,EAAE,MAAM,UAAU,CAAC;AACnE,OAAO,EACL,wBAAwB,EACxB,mBAAmB,EACnB,wBAAwB,EACxB,0BAA0B,EAC1B,2BAA2B,EAC3B,8BAA8B,EAC9B,0BAA0B,EAC1B,uBAAuB,GAExB,MAAM,gBAAgB,CAAC;AAExB,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;AACnC,MAAM,OAAO,GAAG,IAAI,CAAC,CAAC,CAAC,IAAI,MAAM,CAAC;AAClC,MAAM,iBAAiB,GAAG,kCAAkC,CAAC;AAE7D,SAAS,WAAW;IAClB,OAAO,CAAC,GAAG,CAAC;;;;;CAKb,CAAC,CAAC;AACH,CAAC;AAED,SAAS,MAAM,CAAC,QAAgB;IAC9B,MAAM,EAAE,GAAG,eAAe,CAAC;QACzB,KAAK,EAAE,OAAO,CAAC,KAAK;QACpB,MAAM,EAAE,OAAO,CAAC,MAAM;KACvB,CAAC,CAAC;IACH,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE;QAC7B,EAAE,CAAC,QAAQ,CAAC,QAAQ,EAAE,CAAC,MAAM,EAAE,EAAE;YAC/B,EAAE,CAAC,KAAK,EAAE,CAAC;YACX,OAAO,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC,CAAC;QACzB,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;AACL,CAAC;AAED,KAAK,UAAU,OAAO;IACpB,WAAW,EAAE,CAAC;IACd,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,EAAE,CAAC;IAC1B,MAAM,IAAI,GAAG,aAAa,CAAC,GAAG,CAAC,CAAC;IAEhC,OAAO,CAAC,GAAG,CAAC,mBAAmB,CAAC,CAAC;IACjC,OAAO,CAAC,GAAG,CAAC,4BAA4B,IAAI,CAAC,SAAS,EAAE,CAAC,CAAC;IAC1D,OAAO,CAAC,GAAG,CAAC,gCAAgC,IAAI,CAAC,YAAY,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,KAAK,EAAE,CAAC,CAAC;IACjF,OAAO,CAAC,GAAG,CAAC,yBAAyB,IAAI,CAAC,WAAW,IAAI,CAAC,CAAC;IAE3D,IAAI,MAAM,GAAkB,iBAAiB,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;IAChE,MAAM,cAAc,GAAG,IAAI,CAAC,OAAO,CAAC,WAAW,CAAC,CAAC;IACjD,IAAI,cAAc,KAAK,CAAC,CAAC,IAAI,IAAI,CAAC,cAAc,GAAG,CAAC,CAAC,EAAE,CAAC;QACtD,MAAM,GAAG,IAAI,CAAC,cAAc,GAAG,CAAC,CAAC,IAAI,IAAI,CAAC;IAC5C,CAAC;IAED,IAAI,CAAC,MAAM,EAAE,CAAC;QACZ,OAAO,CAAC,GAAG,CAAC,mFAAmF,CAAC,CAAC;QACjG,MAAM,GAAG,CAAC,MAAM,MAAM,CAAC,eAAe,CAAC,CAAC,IAAI,IAAI,CAAC;IACnD,CAAC;IAED,IAAI,MAAM,IAAI,MAAM,KAAK,iBAAiB,EAAE,CAAC;QAC3C,oBAAoB,CAAC,IAAI,CAAC,WAAW,EAAE,cAAc,EAAE,MAAM,CAAC,CAAC;QAC/D,OAAO,CAAC,GAAG,CAAC,oBAAoB,IAAI,CAAC,WAAW,YAAY,CAAC,CAAC;IAChE,CAAC;SAAM,CAAC;QACN,OAAO,CAAC,GAAG,CAAC,gEAAgE,CAAC,CAAC;IAChF,CAAC;IAED,qBAAqB;IACrB,IAAI,eAAe,GAAoB,EAAE,CAAC;IAC1C,IAAI,IAAI,CAAC,SAAS,KAAK,UAAU,EAAE,CAAC;QAClC,eAAe,GAAG,wBAAwB,CAAC,IAAI,CAAC,SAAS,EAAE,IAAI,CAAC,YAAY,CAAC,CAAC;IAChF,CAAC;SAAM,IAAI,IAAI,CAAC,SAAS,KAAK,YAAY,EAAE,CAAC;QAC3C,eAAe,GAAG,0BAA0B,CAC1C,mBAAmB,CAAC,GAAG,CAAC,KAAK,WAAW,EACxC,IAAI,CAAC,YAAY,CAClB,CAAC;IACJ,CAAC;SAAM,IAAI,IAAI,CAAC,SAAS,KAAK,WAAW,EAAE,CAAC;QAC1C,eAAe,GAAG,0BAA0B,EAAE,CAAC;IACjD,CAAC;SAAM,IAAI,IAAI,CAAC,SAAS,KAAK,SAAS,IAAI,IAAI,CAAC,SAAS,KAAK,cAAc,EAAE,CAAC;QAC7E,eAAe,GAAG,wBAAwB,CAAC,IAAI,CAAC,SAAS,EAAE,IAAI,CAAC,YAAY,CAAC,CAAC;IAChF,CAAC;SAAM,IAAI,IAAI,CAAC,SAAS,KAAK,gBAAgB,IAAI,IAAI,CAAC,SAAS,KAAK,eAAe,EAAE,CAAC;QACrF,eAAe,GAAG,8BAA8B,EAAE,CAAC;IACrD,CAAC;SAAM,IAAI,IAAI,CAAC,SAAS,KAAK,IAAI,EAAE,CAAC;QACnC,eAAe,GAAG,mBAAmB,EAAE,CAAC;IAC1C,CAAC;SAAM,IAAI,IAAI,CAAC,SAAS,KAAK,aAAa,EAAE,CAAC;QAC5C,eAAe,GAAG,2BAA2B,EAAE,CAAC;QAChD,IAAI,UAAU,CAAC,IAAI,CAAC,GAAG,EAAE,gBAAgB,CAAC,CAAC,EAAE,CAAC;YAC5C,OAAO,CAAC,GAAG,CAAC,0EAA0E,CAAC,CAAC;YACxF,OAAO,CAAC,GAAG,CAAC,uBAAuB,CAAC,CAAC;QACvC,CAAC;IACH,CAAC;SAAM,IAAI,IAAI,CAAC,SAAS,KAAK,aAAa,EAAE,CAAC;QAC5C,eAAe,GAAG,EAAE,CAAC;QACrB,OAAO,CAAC,GAAG,CAAC,6EAA6E,CAAC,CAAC;QAC3F,OAAO,CAAC,GAAG,CAAC,6DAA6D,CAAC,CAAC;QAC3E,OAAO,CAAC,GAAG,CAAC,yBAAyB,CAAC,CAAC;QACvC,OAAO,CAAC,GAAG,CAAC,oDAAoD,CAAC,CAAC;QAClE,OAAO,CAAC,GAAG,CAAC,sFAAsF,CAAC,CAAC;QACpG,OAAO,CAAC,GAAG,CAAC,mCAAmC,CAAC,CAAC;IACnD,CAAC;SAAM,CAAC;QACN,eAAe,GAAG,wBAAwB,CAAC,IAAI,CAAC,SAAS,EAAE,IAAI,CAAC,YAAY,CAAC,CAAC;IAChF,CAAC;IAED,IAAI,eAAe,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAC/B,OAAO,CAAC,GAAG,CAAC,4CAA4C,CAAC,CAAC;QAC1D,KAAK,MAAM,IAAI,IAAI,eAAe,EAAE,CAAC;YACnC,MAAM,QAAQ,GAAG,IAAI,CAAC,GAAG,EAAE,IAAI,CAAC,IAAI,CAAC,CAAC;YACtC,MAAM,SAAS,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAC;YACpC,IAAI,CAAC,UAAU,CAAC,SAAS,CAAC,EAAE,CAAC;gBAC3B,SAAS,CAAC,SAAS,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;YAC5C,CAAC;YACD,IAAI,CAAC,UAAU,CAAC,QAAQ,CAAC,EAAE,CAAC;gBAC1B,aAAa,CAAC,QAAQ,EAAE,IAAI,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;gBAC9C,OAAO,CAAC,GAAG,CAAC,oBAAoB,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC;YAC/C,CAAC;iBAAM,CAAC;gBACN,OAAO,CAAC,GAAG,CAAC,gCAAgC,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC;YAC3D,CAAC;QACH,CAAC;IACH,CAAC;IAED,IAAI,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC;QAC1B,OAAO,CAAC,GAAG,CAAC,2EAA2E,CAAC,CAAC;QACzF,IAAI,IAAI,CAAC,SAAS,KAAK,aAAa,IAAI,IAAI,CAAC,SAAS,KAAK,aAAa,EAAE,CAAC;YACzE,OAAO,CAAC,GAAG,CAAC,oCAAoC,CAAC,CAAC;YAClD,OAAO,CAAC,GAAG,CAAC,gEAAgE,CAAC,CAAC;YAC9E,OAAO,CAAC,GAAG,CAAC,0GAA0G,CAAC,CAAC;QAC1H,CAAC;aAAM,CAAC;YACN,OAAO,CAAC,GAAG,CAAC,6BAA6B,CAAC,CAAC;YAC3C,OAAO,CAAC,GAAG,CAAC,gEAAgE,CAAC,CAAC;YAC9E,OAAO,CAAC,GAAG,CAAC,+DAA+D,CAAC,CAAC;QAC/E,CAAC;IACH,CAAC;IAED,IAAI,IAAI,CAAC,SAAS,KAAK,aAAa,EAAE,CAAC;QACrC,OAAO,CAAC,GAAG,CAAC;;;CAGf,CAAC,CAAC;IACD,CAAC;SAAM,IAAI,IAAI,CAAC,SAAS,KAAK,aAAa,EAAE,CAAC;QAC5C,OAAO,CAAC,GAAG,CAAC;;;;;;;CAOf,CAAC,CAAC;IACD,CAAC;SAAM,CAAC;QACN,OAAO,CAAC,GAAG,CAAC;;;;;;;CAOf,CAAC,CAAC;IACD,CAAC;AACH,CAAC;AAED,KAAK,UAAU,OAAO;IACpB,MAAM,KAAK,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC;IACtB,IAAI,CAAC,KAAK,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC;QACvC,OAAO,CAAC,GAAG,CAAC,+EAA+E,CAAC,CAAC;QAC7F,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;IAED,MAAM,IAAI,GAAG,aAAa,CAAC,OAAO,CAAC,GAAG,EAAE,CAAC,CAAC;IAC1C,MAAM,MAAM,GAAG,iBAAiB,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;IACnD,IAAI,CAAC,MAAM,EAAE,CAAC;QACZ,OAAO,CAAC,GAAG,CAAC,sFAAsF,CAAC,CAAC;QACpG,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;IAED,OAAO,CAAC,GAAG,CAAC,mCAAmC,KAAK,KAAK,CAAC,CAAC;IAC3D,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC,iCAAiC,EAAE;YACzD,MAAM,EAAE,MAAM;YACd,OAAO,EAAE;gBACP,aAAa,EAAE,UAAU,MAAM,EAAE;gBACjC,cAAc,EAAE,kBAAkB;aACnC;YACD,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,EAAE,KAAK,EAAE,CAAC;SAChC,CAAC,CAAC;QAEH,MAAM,IAAI,GAAG,CAAC,MAAM,GAAG,CAAC,IAAI,EAAE,CAA4D,CAAC;QAC3F,IAAI,GAAG,CAAC,EAAE,EAAE,CAAC;YACX,OAAO,CAAC,GAAG,CAAC,mBAAmB,CAAC,CAAC;YACjC,OAAO,CAAC,GAAG,CAAC,mBAAmB,IAAI,CAAC,UAAU,EAAE,CAAC,CAAC;YAClD,OAAO,CAAC,GAAG,CAAC,gBAAgB,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,qBAAqB,CAAC,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC;QAC7E,CAAC;aAAM,CAAC;YACN,OAAO,CAAC,GAAG,CAAC,yBAAyB,IAAI,CAAC,KAAK,IAAI,GAAG,CAAC,UAAU,EAAE,CAAC,CAAC;QACvE,CAAC;IACH,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,OAAO,CAAC,GAAG,CAAC,gBAAgB,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;IAC7C,CAAC;AACH,CAAC;AAED,KAAK,UAAU,QAAQ;IACrB,MAAM,IAAI,GAAG,aAAa,CAAC,OAAO,CAAC,GAAG,EAAE,CAAC,CAAC;IAC1C,MAAM,MAAM,GAAG,iBAAiB,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;IACnD,IAAI,CAAC,MAAM,EAAE,CAAC;QACZ,OAAO,CAAC,GAAG,CAAC,4CAA4C,CAAC,CAAC;QAC1D,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;IAED,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC,8BAA8B,EAAE;YACtD,MAAM,EAAE,KAAK;YACb,OAAO,EAAE,EAAE,aAAa,EAAE,UAAU,MAAM,EAAE,EAAE;SAC/C,CAAC,CAAC;QACH,MAAM,IAAI,GAAG,CAAC,MAAM,GAAG,CAAC,IAAI,EAAE,CAK7B,CAAC;QACF,IAAI,GAAG,CAAC,EAAE,EAAE,CAAC;YACX,OAAO,CAAC,GAAG,CAAC,qBAAqB,CAAC,CAAC;YACnC,OAAO,CAAC,GAAG,CAAC,uBAAuB,IAAI,CAAC,eAAe,OAAO,IAAI,CAAC,gBAAgB,EAAE,CAAC,CAAC;YACvF,OAAO,CAAC,GAAG,CAAC,uBAAuB,IAAI,CAAC,UAAU,EAAE,CAAC,CAAC;YACtD,OAAO,CAAC,GAAG,CAAC,qBAAqB,IAAI,CAAC,WAAW,CAAC,CAAC,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC,CAAC,SAAS,EAAE,CAAC,CAAC;QACtF,CAAC;aAAM,CAAC;YACN,OAAO,CAAC,GAAG,CAAC,wBAAwB,CAAC,CAAC;QACxC,CAAC;IACH,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,OAAO,CAAC,GAAG,CAAC,gBAAgB,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;IAC7C,CAAC;AACH,CAAC;AAED,IAAI,OAAO,KAAK,WAAW,IAAI,OAAO,KAAK,IAAI,EAAE,CAAC;IAChD,OAAO,CAAC,GAAG,CAAC,eAAe,CAAC,OAAO,CAAC,CAAC;IACrC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC;AAED,IAAI,OAAO,KAAK,QAAQ,IAAI,OAAO,KAAK,IAAI,EAAE,CAAC;IAC7C,OAAO,CAAC,GAAG,CAAC;;;;;;;CAOb,CAAC,CAAC;IACD,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC;AAED,IAAI,OAAO,KAAK,MAAM,EAAE,CAAC;IACvB,OAAO,EAAE,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;AACjC,CAAC;KAAM,IAAI,OAAO,KAAK,MAAM,EAAE,CAAC;IAC9B,OAAO,EAAE,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;AACjC,CAAC;KAAM,IAAI,OAAO,KAAK,OAAO,EAAE,CAAC;IAC/B,QAAQ,EAAE,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;AAClC,CAAC;KAAM,CAAC;IACN,OAAO,CAAC,GAAG,CAAC,mBAAmB,OAAO,qDAAqD,CAAC,CAAC;IAC7F,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export interface GeneratedFile {
|
|
2
|
+
path: string;
|
|
3
|
+
content: string;
|
|
4
|
+
}
|
|
5
|
+
export declare function generateNextAppTemplates(hasSrc: boolean, isTs: boolean): GeneratedFile[];
|
|
6
|
+
export declare function generateNextPagesTemplates(hasSrcPages: boolean, isTs: boolean): GeneratedFile[];
|
|
7
|
+
export declare function generateSvelteKitTemplates(): GeneratedFile[];
|
|
8
|
+
export declare function generateExpressTemplates(hasSrc: boolean, isTs: boolean): GeneratedFile[];
|
|
9
|
+
export declare function generatePythonFastApiTemplates(): GeneratedFile[];
|
|
10
|
+
export declare const phpLaravelRoutesSnippet = "Route::post('/auth/otp/send', [OtpController::class, 'send']);\nRoute::post('/auth/otp/verify', [OtpController::class, 'verify']);\n";
|
|
11
|
+
export declare function generatePhpLaravelTemplates(): GeneratedFile[];
|
|
12
|
+
export declare function generateGoTemplates(): GeneratedFile[];
|
|
@@ -0,0 +1,517 @@
|
|
|
1
|
+
export function generateNextAppTemplates(hasSrc, isTs) {
|
|
2
|
+
const prefix = hasSrc ? "src/" : "";
|
|
3
|
+
const ext = isTs ? "ts" : "js";
|
|
4
|
+
const clientCode = isTs
|
|
5
|
+
? `import { OtpyClient } from "@o-t-p-y/sdk";
|
|
6
|
+
|
|
7
|
+
export const otpy = new OtpyClient({
|
|
8
|
+
apiKey: process.env.OTPY_API_KEY!,
|
|
9
|
+
});
|
|
10
|
+
`
|
|
11
|
+
: `import { OtpyClient } from "@o-t-p-y/sdk";
|
|
12
|
+
|
|
13
|
+
export const otpy = new OtpyClient({
|
|
14
|
+
apiKey: process.env.OTPY_API_KEY,
|
|
15
|
+
});
|
|
16
|
+
`;
|
|
17
|
+
const sendRouteCode = isTs
|
|
18
|
+
? `import { NextResponse } from "next/server";
|
|
19
|
+
import { otpy } from "@/lib/otpy";
|
|
20
|
+
|
|
21
|
+
export async function POST(request: Request) {
|
|
22
|
+
try {
|
|
23
|
+
const { phone } = await request.json();
|
|
24
|
+
if (!phone) {
|
|
25
|
+
return NextResponse.json({ error: "شماره موبایل الزامی است" }, { status: 400 });
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const result = await otpy.sendOtp(phone);
|
|
29
|
+
return NextResponse.json(result);
|
|
30
|
+
} catch (error: any) {
|
|
31
|
+
return NextResponse.json(
|
|
32
|
+
{ error: error.message || "خطا در ارسال پیامک" },
|
|
33
|
+
{ status: error.status || 500 }
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
`
|
|
38
|
+
: `import { NextResponse } from "next/server";
|
|
39
|
+
import { otpy } from "@/lib/otpy";
|
|
40
|
+
|
|
41
|
+
export async function POST(request) {
|
|
42
|
+
try {
|
|
43
|
+
const { phone } = await request.json();
|
|
44
|
+
if (!phone) {
|
|
45
|
+
return NextResponse.json({ error: "شماره موبایل الزامی است" }, { status: 400 });
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const result = await otpy.sendOtp(phone);
|
|
49
|
+
return NextResponse.json(result);
|
|
50
|
+
} catch (error) {
|
|
51
|
+
return NextResponse.json(
|
|
52
|
+
{ error: error.message || "خطا در ارسال پیامک" },
|
|
53
|
+
{ status: error.status || 500 }
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
`;
|
|
58
|
+
const verifyRouteCode = isTs
|
|
59
|
+
? `import { NextResponse } from "next/server";
|
|
60
|
+
import { otpy } from "@/lib/otpy";
|
|
61
|
+
|
|
62
|
+
export async function POST(request: Request) {
|
|
63
|
+
try {
|
|
64
|
+
const { phone, code } = await request.json();
|
|
65
|
+
if (!phone || !code) {
|
|
66
|
+
return NextResponse.json({ error: "شماره موبایل و کد ورود الزامی است" }, { status: 400 });
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const result = await otpy.verifyOtp(phone, code);
|
|
70
|
+
if (!result.verified) {
|
|
71
|
+
return NextResponse.json({ verified: false, error: "کد وارد شده اشتباه یا منقضی است" }, { status: 401 });
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// TODO: احراز هویت موفق بود. اینجا سشن یا توکن لاگین کاربر را صادر کنید.
|
|
75
|
+
return NextResponse.json({ verified: true });
|
|
76
|
+
} catch (error: any) {
|
|
77
|
+
return NextResponse.json(
|
|
78
|
+
{ error: error.message || "خطا در بررسی کد" },
|
|
79
|
+
{ status: error.status || 500 }
|
|
80
|
+
);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
`
|
|
84
|
+
: `import { NextResponse } from "next/server";
|
|
85
|
+
import { otpy } from "@/lib/otpy";
|
|
86
|
+
|
|
87
|
+
export async function POST(request) {
|
|
88
|
+
try {
|
|
89
|
+
const { phone, code } = await request.json();
|
|
90
|
+
if (!phone || !code) {
|
|
91
|
+
return NextResponse.json({ error: "شماره موبایل و کد ورود الزامی است" }, { status: 400 });
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const result = await otpy.verifyOtp(phone, code);
|
|
95
|
+
if (!result.verified) {
|
|
96
|
+
return NextResponse.json({ verified: false, error: "کد وارد شده اشتباه یا منقضی است" }, { status: 401 });
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// TODO: احراز هویت موفق بود. اینجا سشن یا توکن لاگین کاربر را صادر کنید.
|
|
100
|
+
return NextResponse.json({ verified: true });
|
|
101
|
+
} catch (error) {
|
|
102
|
+
return NextResponse.json(
|
|
103
|
+
{ error: error.message || "خطا در بررسی کد" },
|
|
104
|
+
{ status: error.status || 500 }
|
|
105
|
+
);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
`;
|
|
109
|
+
return [
|
|
110
|
+
{ path: `${prefix}lib/otpy.${ext}`, content: clientCode },
|
|
111
|
+
{ path: `${prefix}app/api/auth/otp/send/route.${ext}`, content: sendRouteCode },
|
|
112
|
+
{ path: `${prefix}app/api/auth/otp/verify/route.${ext}`, content: verifyRouteCode },
|
|
113
|
+
];
|
|
114
|
+
}
|
|
115
|
+
export function generateNextPagesTemplates(hasSrcPages, isTs) {
|
|
116
|
+
const prefix = hasSrcPages ? "src/" : "";
|
|
117
|
+
const ext = isTs ? "ts" : "js";
|
|
118
|
+
const clientCode = isTs
|
|
119
|
+
? `import { OtpyClient } from "@o-t-p-y/sdk";
|
|
120
|
+
|
|
121
|
+
export const otpy = new OtpyClient({
|
|
122
|
+
apiKey: process.env.OTPY_API_KEY!,
|
|
123
|
+
});
|
|
124
|
+
`
|
|
125
|
+
: `import { OtpyClient } from "@o-t-p-y/sdk";
|
|
126
|
+
|
|
127
|
+
export const otpy = new OtpyClient({
|
|
128
|
+
apiKey: process.env.OTPY_API_KEY,
|
|
129
|
+
});
|
|
130
|
+
`;
|
|
131
|
+
const sendHandlerCode = isTs
|
|
132
|
+
? `import type { NextApiRequest, NextApiResponse } from "next";
|
|
133
|
+
import { OtpyError } from "@o-t-p-y/sdk";
|
|
134
|
+
import { otpy } from "../../../../lib/otpy";
|
|
135
|
+
|
|
136
|
+
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
|
|
137
|
+
if (req.method !== "POST") {
|
|
138
|
+
res.setHeader("Allow", "POST");
|
|
139
|
+
return res.status(405).json({ error: "فقط متد POST مجاز است" });
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
try {
|
|
143
|
+
const { phone } = req.body;
|
|
144
|
+
if (!phone) {
|
|
145
|
+
return res.status(400).json({ error: "شماره موبایل الزامی است" });
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
const result = await otpy.sendOtp(phone);
|
|
149
|
+
return res.status(200).json(result);
|
|
150
|
+
} catch (error) {
|
|
151
|
+
if (error instanceof OtpyError) {
|
|
152
|
+
return res.status(error.status || 500).json({ error: error.code });
|
|
153
|
+
}
|
|
154
|
+
return res.status(500).json({ error: "خطا در ارسال پیامک" });
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
`
|
|
158
|
+
: `import { OtpyError } from "@o-t-p-y/sdk";
|
|
159
|
+
import { otpy } from "../../../../lib/otpy";
|
|
160
|
+
|
|
161
|
+
export default async function handler(req, res) {
|
|
162
|
+
if (req.method !== "POST") {
|
|
163
|
+
res.setHeader("Allow", "POST");
|
|
164
|
+
return res.status(405).json({ error: "فقط متد POST مجاز است" });
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
try {
|
|
168
|
+
const { phone } = req.body;
|
|
169
|
+
if (!phone) {
|
|
170
|
+
return res.status(400).json({ error: "شماره موبایل الزامی است" });
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
const result = await otpy.sendOtp(phone);
|
|
174
|
+
return res.status(200).json(result);
|
|
175
|
+
} catch (error) {
|
|
176
|
+
if (error instanceof OtpyError) {
|
|
177
|
+
return res.status(error.status || 500).json({ error: error.code });
|
|
178
|
+
}
|
|
179
|
+
return res.status(500).json({ error: "خطا در ارسال پیامک" });
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
`;
|
|
183
|
+
const verifyHandlerCode = isTs
|
|
184
|
+
? `import type { NextApiRequest, NextApiResponse } from "next";
|
|
185
|
+
import { OtpyError } from "@o-t-p-y/sdk";
|
|
186
|
+
import { otpy } from "../../../../lib/otpy";
|
|
187
|
+
|
|
188
|
+
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
|
|
189
|
+
if (req.method !== "POST") {
|
|
190
|
+
res.setHeader("Allow", "POST");
|
|
191
|
+
return res.status(405).json({ error: "فقط متد POST مجاز است" });
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
try {
|
|
195
|
+
const { phone, code } = req.body;
|
|
196
|
+
if (!phone || !code) {
|
|
197
|
+
return res.status(400).json({ error: "شماره موبایل و کد ورود الزامی است" });
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
const result = await otpy.verifyOtp(phone, code);
|
|
201
|
+
if (!result.verified) {
|
|
202
|
+
return res.status(401).json({ verified: false, error: "کد وارد شده اشتباه یا منقضی است" });
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// TODO: احراز هویت موفق بود. اینجا سشن یا توکن لاگین کاربر را صادر کنید.
|
|
206
|
+
return res.status(200).json({ verified: true });
|
|
207
|
+
} catch (error) {
|
|
208
|
+
if (error instanceof OtpyError) {
|
|
209
|
+
return res.status(error.status || 500).json({ error: error.code });
|
|
210
|
+
}
|
|
211
|
+
return res.status(500).json({ error: "خطا در بررسی کد" });
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
`
|
|
215
|
+
: `import { OtpyError } from "@o-t-p-y/sdk";
|
|
216
|
+
import { otpy } from "../../../../lib/otpy";
|
|
217
|
+
|
|
218
|
+
export default async function handler(req, res) {
|
|
219
|
+
if (req.method !== "POST") {
|
|
220
|
+
res.setHeader("Allow", "POST");
|
|
221
|
+
return res.status(405).json({ error: "فقط متد POST مجاز است" });
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
try {
|
|
225
|
+
const { phone, code } = req.body;
|
|
226
|
+
if (!phone || !code) {
|
|
227
|
+
return res.status(400).json({ error: "شماره موبایل و کد ورود الزامی است" });
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
const result = await otpy.verifyOtp(phone, code);
|
|
231
|
+
if (!result.verified) {
|
|
232
|
+
return res.status(401).json({ verified: false, error: "کد وارد شده اشتباه یا منقضی است" });
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// TODO: احراز هویت موفق بود. اینجا سشن یا توکن لاگین کاربر را صادر کنید.
|
|
236
|
+
return res.status(200).json({ verified: true });
|
|
237
|
+
} catch (error) {
|
|
238
|
+
if (error instanceof OtpyError) {
|
|
239
|
+
return res.status(error.status || 500).json({ error: error.code });
|
|
240
|
+
}
|
|
241
|
+
return res.status(500).json({ error: "خطا در بررسی کد" });
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
`;
|
|
245
|
+
return [
|
|
246
|
+
{ path: `${prefix}lib/otpy.${ext}`, content: clientCode },
|
|
247
|
+
{ path: `${prefix}pages/api/auth/otp/send.${ext}`, content: sendHandlerCode },
|
|
248
|
+
{ path: `${prefix}pages/api/auth/otp/verify.${ext}`, content: verifyHandlerCode },
|
|
249
|
+
];
|
|
250
|
+
}
|
|
251
|
+
export function generateSvelteKitTemplates() {
|
|
252
|
+
const clientCode = `import { OtpyClient } from "@o-t-p-y/sdk";
|
|
253
|
+
import { env } from "$env/dynamic/private";
|
|
254
|
+
|
|
255
|
+
export const otpy = new OtpyClient({
|
|
256
|
+
apiKey: env.OTPY_API_KEY ?? "",
|
|
257
|
+
});
|
|
258
|
+
`;
|
|
259
|
+
const sendRouteCode = `import { json, type RequestHandler } from "@sveltejs/kit";
|
|
260
|
+
import { OtpyError } from "@o-t-p-y/sdk";
|
|
261
|
+
import { otpy } from "$lib/otpy";
|
|
262
|
+
|
|
263
|
+
export const POST: RequestHandler = async ({ request }) => {
|
|
264
|
+
try {
|
|
265
|
+
const { phone } = (await request.json()) as { phone?: string };
|
|
266
|
+
if (!phone) {
|
|
267
|
+
return json({ error: "شماره موبایل الزامی است" }, { status: 400 });
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
const result = await otpy.sendOtp(phone);
|
|
271
|
+
return json(result);
|
|
272
|
+
} catch (error) {
|
|
273
|
+
if (error instanceof OtpyError) {
|
|
274
|
+
return json({ error: error.code }, { status: error.status || 500 });
|
|
275
|
+
}
|
|
276
|
+
return json({ error: "خطا در ارسال پیامک" }, { status: 500 });
|
|
277
|
+
}
|
|
278
|
+
};
|
|
279
|
+
`;
|
|
280
|
+
const verifyRouteCode = `import { json, type RequestHandler } from "@sveltejs/kit";
|
|
281
|
+
import { OtpyError } from "@o-t-p-y/sdk";
|
|
282
|
+
import { otpy } from "$lib/otpy";
|
|
283
|
+
|
|
284
|
+
export const POST: RequestHandler = async ({ request }) => {
|
|
285
|
+
try {
|
|
286
|
+
const { phone, code } = (await request.json()) as { phone?: string; code?: string };
|
|
287
|
+
if (!phone || !code) {
|
|
288
|
+
return json({ error: "شماره موبایل و کد ورود الزامی است" }, { status: 400 });
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
const result = await otpy.verifyOtp(phone, code);
|
|
292
|
+
if (!result.verified) {
|
|
293
|
+
return json({ verified: false, error: "کد وارد شده اشتباه یا منقضی است" }, { status: 401 });
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
// TODO: احراز هویت موفق بود. اینجا سشن یا توکن لاگین کاربر را صادر کنید.
|
|
297
|
+
return json({ verified: true });
|
|
298
|
+
} catch (error) {
|
|
299
|
+
if (error instanceof OtpyError) {
|
|
300
|
+
return json({ error: error.code }, { status: error.status || 500 });
|
|
301
|
+
}
|
|
302
|
+
return json({ error: "خطا در بررسی کد" }, { status: 500 });
|
|
303
|
+
}
|
|
304
|
+
};
|
|
305
|
+
`;
|
|
306
|
+
return [
|
|
307
|
+
{ path: "src/lib/otpy.ts", content: clientCode },
|
|
308
|
+
{ path: "src/routes/auth/otp/send/+server.ts", content: sendRouteCode },
|
|
309
|
+
{ path: "src/routes/auth/otp/verify/+server.ts", content: verifyRouteCode },
|
|
310
|
+
];
|
|
311
|
+
}
|
|
312
|
+
export function generateExpressTemplates(hasSrc, isTs) {
|
|
313
|
+
const prefix = hasSrc ? "src/" : "";
|
|
314
|
+
const ext = isTs ? "ts" : "js";
|
|
315
|
+
const clientCode = `import { OtpyClient } from "@o-t-p-y/sdk";
|
|
316
|
+
|
|
317
|
+
export const otpy = new OtpyClient({
|
|
318
|
+
apiKey: process.env.OTPY_API_KEY || "",
|
|
319
|
+
});
|
|
320
|
+
`;
|
|
321
|
+
const routerCode = `import { Router } from "express";
|
|
322
|
+
import { otpy } from "../lib/otpy.js";
|
|
323
|
+
|
|
324
|
+
const router = Router();
|
|
325
|
+
|
|
326
|
+
router.post("/send", async (req, res) => {
|
|
327
|
+
try {
|
|
328
|
+
const { phone } = req.body;
|
|
329
|
+
if (!phone) return res.status(400).json({ error: "شماره موبایل الزامی است" });
|
|
330
|
+
const result = await otpy.sendOtp(phone);
|
|
331
|
+
return res.json(result);
|
|
332
|
+
} catch (err) {
|
|
333
|
+
return res.status(err.status || 500).json({ error: err.code || "خطا در ارسال" });
|
|
334
|
+
}
|
|
335
|
+
});
|
|
336
|
+
|
|
337
|
+
router.post("/verify", async (req, res) => {
|
|
338
|
+
try {
|
|
339
|
+
const { phone, code } = req.body;
|
|
340
|
+
if (!phone || !code) return res.status(400).json({ error: "شماره موبایل و کد الزامی است" });
|
|
341
|
+
const result = await otpy.verifyOtp(phone, code);
|
|
342
|
+
return res.json(result);
|
|
343
|
+
} catch (err) {
|
|
344
|
+
return res.status(err.status || 500).json({ error: err.code || "خطا در تایید" });
|
|
345
|
+
}
|
|
346
|
+
});
|
|
347
|
+
|
|
348
|
+
export default router;
|
|
349
|
+
`;
|
|
350
|
+
return [
|
|
351
|
+
{ path: `${prefix}lib/otpy.${ext}`, content: clientCode },
|
|
352
|
+
{ path: `${prefix}routes/otp.${ext}`, content: routerCode },
|
|
353
|
+
];
|
|
354
|
+
}
|
|
355
|
+
export function generatePythonFastApiTemplates() {
|
|
356
|
+
const code = `print("Install dependencies: pip install requests fastapi")
|
|
357
|
+
|
|
358
|
+
import os
|
|
359
|
+
import requests
|
|
360
|
+
from fastapi import APIRouter, HTTPException
|
|
361
|
+
from pydantic import BaseModel
|
|
362
|
+
|
|
363
|
+
router = APIRouter(prefix="/auth/otp", tags=["OTP"])
|
|
364
|
+
|
|
365
|
+
OTPY_API_KEY = os.getenv("OTPY_API_KEY", "")
|
|
366
|
+
HEADERS = {
|
|
367
|
+
"Authorization": f"Bearer {OTPY_API_KEY}",
|
|
368
|
+
"Content-Type": "application/json"
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
class SendOtpRequest(BaseModel):
|
|
372
|
+
phone: str
|
|
373
|
+
|
|
374
|
+
class VerifyOtpRequest(BaseModel):
|
|
375
|
+
phone: str
|
|
376
|
+
code: str
|
|
377
|
+
|
|
378
|
+
@router.post("/send")
|
|
379
|
+
def send_otp(req: SendOtpRequest):
|
|
380
|
+
res = requests.post("https://api.otpy.ir/v1/otp/send", json={"phone": req.phone}, headers=HEADERS)
|
|
381
|
+
if not res.ok:
|
|
382
|
+
raise HTTPException(status_code=res.status_code, detail=res.json().get("error", "Failed"))
|
|
383
|
+
return res.json()
|
|
384
|
+
|
|
385
|
+
@router.post("/verify")
|
|
386
|
+
def verify_otp(req: VerifyOtpRequest):
|
|
387
|
+
res = requests.post("https://api.otpy.ir/v1/otp/verify", json={"phone": req.phone, "code": req.code}, headers=HEADERS)
|
|
388
|
+
if not res.ok:
|
|
389
|
+
raise HTTPException(status_code=res.status_code, detail=res.json().get("error", "Failed"))
|
|
390
|
+
return res.json()
|
|
391
|
+
`;
|
|
392
|
+
return [{ path: "routers/otp.py", content: code }];
|
|
393
|
+
}
|
|
394
|
+
export const phpLaravelRoutesSnippet = `Route::post('/auth/otp/send', [OtpController::class, 'send']);
|
|
395
|
+
Route::post('/auth/otp/verify', [OtpController::class, 'verify']);
|
|
396
|
+
`;
|
|
397
|
+
export function generatePhpLaravelTemplates() {
|
|
398
|
+
const configCode = `<?php
|
|
399
|
+
|
|
400
|
+
return [
|
|
401
|
+
'key' => env('OTPY_API_KEY', ''),
|
|
402
|
+
'base_url' => env('OTPY_BASE_URL', 'https://api.otpy.ir'),
|
|
403
|
+
];
|
|
404
|
+
`;
|
|
405
|
+
const controllerCode = `<?php
|
|
406
|
+
|
|
407
|
+
namespace App\\Http\\Controllers;
|
|
408
|
+
|
|
409
|
+
use Illuminate\\Http\\JsonResponse;
|
|
410
|
+
use Illuminate\\Http\\Request;
|
|
411
|
+
use Illuminate\\Support\\Facades\\Http;
|
|
412
|
+
|
|
413
|
+
class OtpController extends Controller
|
|
414
|
+
{
|
|
415
|
+
public function send(Request $request): JsonResponse
|
|
416
|
+
{
|
|
417
|
+
$validated = $request->validate([
|
|
418
|
+
'phone' => 'required|string',
|
|
419
|
+
]);
|
|
420
|
+
|
|
421
|
+
$response = Http::withToken(config('otpy.key'))
|
|
422
|
+
->acceptJson()
|
|
423
|
+
->post(config('otpy.base_url').'/v1/otp/send', [
|
|
424
|
+
'phone' => $validated['phone'],
|
|
425
|
+
]);
|
|
426
|
+
|
|
427
|
+
if ($response->failed()) {
|
|
428
|
+
return response()->json(
|
|
429
|
+
['error' => $response->json('error', 'خطا در ارسال پیامک')],
|
|
430
|
+
$response->status()
|
|
431
|
+
);
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
return response()->json($response->json());
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
public function verify(Request $request): JsonResponse
|
|
438
|
+
{
|
|
439
|
+
$validated = $request->validate([
|
|
440
|
+
'phone' => 'required|string',
|
|
441
|
+
'code' => 'required|string',
|
|
442
|
+
]);
|
|
443
|
+
|
|
444
|
+
$response = Http::withToken(config('otpy.key'))
|
|
445
|
+
->acceptJson()
|
|
446
|
+
->post(config('otpy.base_url').'/v1/otp/verify', [
|
|
447
|
+
'phone' => $validated['phone'],
|
|
448
|
+
'code' => $validated['code'],
|
|
449
|
+
]);
|
|
450
|
+
|
|
451
|
+
if ($response->failed()) {
|
|
452
|
+
return response()->json(
|
|
453
|
+
['error' => $response->json('error', 'خطا در بررسی کد')],
|
|
454
|
+
$response->status()
|
|
455
|
+
);
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
// TODO: احراز هویت موفق بود. اینجا سشن یا توکن لاگین کاربر را صادر کنید.
|
|
459
|
+
return response()->json(['verified' => (bool) $response->json('verified', false)]);
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
`;
|
|
463
|
+
const routesCode = `<?php
|
|
464
|
+
|
|
465
|
+
use App\\Http\\Controllers\\OtpController;
|
|
466
|
+
use Illuminate\\Support\\Facades\\Route;
|
|
467
|
+
|
|
468
|
+
${phpLaravelRoutesSnippet}`;
|
|
469
|
+
return [
|
|
470
|
+
{ path: "config/otpy.php", content: configCode },
|
|
471
|
+
{ path: "app/Http/Controllers/OtpController.php", content: controllerCode },
|
|
472
|
+
{ path: "routes/api.php", content: routesCode },
|
|
473
|
+
];
|
|
474
|
+
}
|
|
475
|
+
export function generateGoTemplates() {
|
|
476
|
+
const code = `package otpy
|
|
477
|
+
|
|
478
|
+
import (
|
|
479
|
+
"bytes"
|
|
480
|
+
"encoding/json"
|
|
481
|
+
"fmt"
|
|
482
|
+
"net/http"
|
|
483
|
+
"os"
|
|
484
|
+
)
|
|
485
|
+
|
|
486
|
+
type OtpClient struct {
|
|
487
|
+
ApiKey string
|
|
488
|
+
BaseUrl string
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
func NewClient() *OtpClient {
|
|
492
|
+
return &OtpClient{
|
|
493
|
+
ApiKey: os.Getenv("OTPY_API_KEY"),
|
|
494
|
+
BaseUrl: "https://api.otpy.ir",
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
func (c *OtpClient) Send(phone string) (map[string]any, error) {
|
|
499
|
+
payload, _ := json.Marshal(map[string]string{"phone": phone})
|
|
500
|
+
req, _ := http.NewRequest("POST", c.BaseUrl+"/v1/otp/send", bytes.NewBuffer(payload))
|
|
501
|
+
req.Header.Set("Authorization", "Bearer "+c.ApiKey)
|
|
502
|
+
req.Header.Set("Content-Type", "application/json")
|
|
503
|
+
|
|
504
|
+
resp, err := http.DefaultClient.Do(req)
|
|
505
|
+
if err != nil {
|
|
506
|
+
return nil, err
|
|
507
|
+
}
|
|
508
|
+
defer resp.Body.Close()
|
|
509
|
+
|
|
510
|
+
var result map[string]any
|
|
511
|
+
json.NewDecoder(resp.Body).Decode(&result)
|
|
512
|
+
return result, nil
|
|
513
|
+
}
|
|
514
|
+
`;
|
|
515
|
+
return [{ path: "pkg/otpy/client.go", content: code }];
|
|
516
|
+
}
|
|
517
|
+
//# sourceMappingURL=templates.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"templates.js","sourceRoot":"","sources":["../src/templates.ts"],"names":[],"mappings":"AAKA,MAAM,UAAU,wBAAwB,CAAC,MAAe,EAAE,IAAa;IACrE,MAAM,MAAM,GAAG,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC;IACpC,MAAM,GAAG,GAAG,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC;IAE/B,MAAM,UAAU,GAAG,IAAI;QACrB,CAAC,CAAC;;;;;CAKL;QACG,CAAC,CAAC;;;;;CAKL,CAAC;IAEA,MAAM,aAAa,GAAG,IAAI;QACxB,CAAC,CAAC;;;;;;;;;;;;;;;;;;;CAmBL;QACG,CAAC,CAAC;;;;;;;;;;;;;;;;;;;CAmBL,CAAC;IAEA,MAAM,eAAe,GAAG,IAAI;QAC1B,CAAC,CAAC;;;;;;;;;;;;;;;;;;;;;;;;CAwBL;QACG,CAAC,CAAC;;;;;;;;;;;;;;;;;;;;;;;;CAwBL,CAAC;IAEA,OAAO;QACL,EAAE,IAAI,EAAE,GAAG,MAAM,YAAY,GAAG,EAAE,EAAE,OAAO,EAAE,UAAU,EAAE;QACzD,EAAE,IAAI,EAAE,GAAG,MAAM,+BAA+B,GAAG,EAAE,EAAE,OAAO,EAAE,aAAa,EAAE;QAC/E,EAAE,IAAI,EAAE,GAAG,MAAM,iCAAiC,GAAG,EAAE,EAAE,OAAO,EAAE,eAAe,EAAE;KACpF,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,0BAA0B,CAAC,WAAoB,EAAE,IAAa;IAC5E,MAAM,MAAM,GAAG,WAAW,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC;IACzC,MAAM,GAAG,GAAG,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC;IAE/B,MAAM,UAAU,GAAG,IAAI;QACrB,CAAC,CAAC;;;;;CAKL;QACG,CAAC,CAAC;;;;;CAKL,CAAC;IAEA,MAAM,eAAe,GAAG,IAAI;QAC1B,CAAC,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;CAyBL;QACG,CAAC,CAAC;;;;;;;;;;;;;;;;;;;;;;;;CAwBL,CAAC;IAEA,MAAM,iBAAiB,GAAG,IAAI;QAC5B,CAAC,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA8BL;QACG,CAAC,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA6BL,CAAC;IAEA,OAAO;QACL,EAAE,IAAI,EAAE,GAAG,MAAM,YAAY,GAAG,EAAE,EAAE,OAAO,EAAE,UAAU,EAAE;QACzD,EAAE,IAAI,EAAE,GAAG,MAAM,2BAA2B,GAAG,EAAE,EAAE,OAAO,EAAE,eAAe,EAAE;QAC7E,EAAE,IAAI,EAAE,GAAG,MAAM,6BAA6B,GAAG,EAAE,EAAE,OAAO,EAAE,iBAAiB,EAAE;KAClF,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,0BAA0B;IACxC,MAAM,UAAU,GAAG;;;;;;CAMpB,CAAC;IAEA,MAAM,aAAa,GAAG;;;;;;;;;;;;;;;;;;;;CAoBvB,CAAC;IAEA,MAAM,eAAe,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;CAyBzB,CAAC;IAEA,OAAO;QACL,EAAE,IAAI,EAAE,iBAAiB,EAAE,OAAO,EAAE,UAAU,EAAE;QAChD,EAAE,IAAI,EAAE,qCAAqC,EAAE,OAAO,EAAE,aAAa,EAAE;QACvE,EAAE,IAAI,EAAE,uCAAuC,EAAE,OAAO,EAAE,eAAe,EAAE;KAC5E,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,wBAAwB,CAAC,MAAe,EAAE,IAAa;IACrE,MAAM,MAAM,GAAG,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC;IACpC,MAAM,GAAG,GAAG,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC;IAE/B,MAAM,UAAU,GAAG;;;;;CAKpB,CAAC;IAEA,MAAM,UAAU,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA4BpB,CAAC;IAEA,OAAO;QACL,EAAE,IAAI,EAAE,GAAG,MAAM,YAAY,GAAG,EAAE,EAAE,OAAO,EAAE,UAAU,EAAE;QACzD,EAAE,IAAI,EAAE,GAAG,MAAM,cAAc,GAAG,EAAE,EAAE,OAAO,EAAE,UAAU,EAAE;KAC5D,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,8BAA8B;IAC5C,MAAM,IAAI,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAmCd,CAAC;IAEA,OAAO,CAAC,EAAE,IAAI,EAAE,gBAAgB,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC;AACrD,CAAC;AAED,MAAM,CAAC,MAAM,uBAAuB,GAAG;;CAEtC,CAAC;AAEF,MAAM,UAAU,2BAA2B;IACzC,MAAM,UAAU,GAAG;;;;;;CAMpB,CAAC;IAEA,MAAM,cAAc,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAyDxB,CAAC;IAEA,MAAM,UAAU,GAAG;;;;;EAKnB,uBAAuB,EAAE,CAAC;IAE1B,OAAO;QACL,EAAE,IAAI,EAAE,iBAAiB,EAAE,OAAO,EAAE,UAAU,EAAE;QAChD,EAAE,IAAI,EAAE,wCAAwC,EAAE,OAAO,EAAE,cAAc,EAAE;QAC3E,EAAE,IAAI,EAAE,gBAAgB,EAAE,OAAO,EAAE,UAAU,EAAE;KAChD,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,mBAAmB;IACjC,MAAM,IAAI,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAsCd,CAAC;IAEA,OAAO,CAAC,EAAE,IAAI,EAAE,oBAAoB,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC;AACzD,CAAC"}
|
package/package.json
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@o-t-p-y/cli",
|
|
3
|
+
"version": "0.3.2",
|
|
4
|
+
"private": false,
|
|
5
|
+
"type": "module",
|
|
6
|
+
"description": "npx otpy-cli init — one-command OTPy integration (framework detection, code patching, AI mode).",
|
|
7
|
+
"license": "MIT",
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "https://github.com/o-t-p-y/cli.git",
|
|
11
|
+
"directory": "packages/cli"
|
|
12
|
+
},
|
|
13
|
+
"homepage": "https://otpy.ir",
|
|
14
|
+
"bugs": "https://github.com/o-t-p-y/cli/issues",
|
|
15
|
+
"keywords": [
|
|
16
|
+
"otp",
|
|
17
|
+
"otpy",
|
|
18
|
+
"sms",
|
|
19
|
+
"iranian-otp",
|
|
20
|
+
"cli",
|
|
21
|
+
"authentication"
|
|
22
|
+
],
|
|
23
|
+
"bin": {
|
|
24
|
+
"otpy-cli": "./dist/index.js"
|
|
25
|
+
},
|
|
26
|
+
"files": [
|
|
27
|
+
"dist"
|
|
28
|
+
],
|
|
29
|
+
"publishConfig": {
|
|
30
|
+
"access": "public"
|
|
31
|
+
},
|
|
32
|
+
"engines": {
|
|
33
|
+
"node": ">=18"
|
|
34
|
+
},
|
|
35
|
+
"scripts": {
|
|
36
|
+
"build": "tsc -p tsconfig.json",
|
|
37
|
+
"typecheck": "tsc --noEmit",
|
|
38
|
+
"dev": "tsx src/index.ts",
|
|
39
|
+
"test": "vitest run --passWithNoTests"
|
|
40
|
+
},
|
|
41
|
+
"devDependencies": {
|
|
42
|
+
"@types/node": "^22.10.2",
|
|
43
|
+
"tsx": "^4.19.2",
|
|
44
|
+
"typescript": "^5.7.2",
|
|
45
|
+
"vitest": "^3.2.4"
|
|
46
|
+
}
|
|
47
|
+
}
|