create-apiagex 0.6.1
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 +127 -0
- package/dist/args.d.ts +4 -0
- package/dist/args.d.ts.map +1 -0
- package/dist/args.js +62 -0
- package/dist/create-apiagex.type.d.ts +44 -0
- package/dist/create-apiagex.type.d.ts.map +1 -0
- package/dist/create-apiagex.type.js +1 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +87 -0
- package/dist/prompts.d.ts +3 -0
- package/dist/prompts.d.ts.map +1 -0
- package/dist/prompts.js +47 -0
- package/dist/scaffold.d.ts +4 -0
- package/dist/scaffold.d.ts.map +1 -0
- package/dist/scaffold.js +175 -0
- package/package.json +45 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Aditya Gupta and contributors
|
|
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,127 @@
|
|
|
1
|
+
# create-apiagex
|
|
2
|
+
|
|
3
|
+
## English
|
|
4
|
+
|
|
5
|
+
`create-apiagex` is the installer CLI for starting a new Apiagex project.
|
|
6
|
+
|
|
7
|
+
Current CLI behavior:
|
|
8
|
+
|
|
9
|
+
- Prints help with `create-apiagex --help`.
|
|
10
|
+
- Prints version with `create-apiagex --version`.
|
|
11
|
+
- Prompts for project name and setup choices in an interactive terminal.
|
|
12
|
+
- Supports `--yes` and setup flags for CI/non-interactive scaffolding.
|
|
13
|
+
- Validates that the target folder is a safe slug like `my-cms`.
|
|
14
|
+
- Refuses to overwrite an existing non-empty folder.
|
|
15
|
+
- Supports `--dry-run` to show the scaffold plan without writing files.
|
|
16
|
+
- Creates a small starter scaffold when the target folder is missing or empty.
|
|
17
|
+
|
|
18
|
+
Interactive setup asks for setup mode, package manager, dependency install preference, git init preference, and owner bootstrap preference.
|
|
19
|
+
|
|
20
|
+
Generated starter files:
|
|
21
|
+
|
|
22
|
+
- `package.json`
|
|
23
|
+
- `.gitignore`
|
|
24
|
+
- `.env.example`
|
|
25
|
+
- `apiagex.config.json`
|
|
26
|
+
- `README.md`
|
|
27
|
+
- `docs/README.md`
|
|
28
|
+
|
|
29
|
+
The generated `.env.example` documents `APIAGEX_DATABASE_PATH=.apiagex/apiagex.sqlite` and `APIAGEX_UPLOADS_PATH=.apiagex/uploads` for local persistence.
|
|
30
|
+
|
|
31
|
+
The generated `package.json` depends on `apiagex-server` and exposes `npm run dev`, `npm run start`, `npm run smoke`, and `npm run build`.
|
|
32
|
+
|
|
33
|
+
The generated starter README points users to `/doc`, `/readme`, and `/adminui`, includes practical owner/schema/entry/role/webhook/realtime flow, and explains common errors.
|
|
34
|
+
|
|
35
|
+
The generated `docs/README.md` explains generated API shape, access control, webhooks, realtime, relation docs, payloads, populate options, Admin UI entry pickers, and common errors.
|
|
36
|
+
|
|
37
|
+
The generated-project test verifies this scaffold without network installs by creating a temporary project, running the runtime smoke command, starting Apiagex on a temporary port/database, and checking `/api/health`, `/adminui`, `/doc`, and `/readme`.
|
|
38
|
+
|
|
39
|
+
Example:
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
npm create apiagex@latest my-cms
|
|
43
|
+
npx create-apiagex my-cms
|
|
44
|
+
npm run build -w create-apiagex
|
|
45
|
+
node packages/create-apiagex/dist/index.js my-cms --dry-run
|
|
46
|
+
node packages/create-apiagex/dist/index.js my-cms --yes
|
|
47
|
+
node packages/create-apiagex/dist/index.js --dry-run
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
Local workspace test before publishing:
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
npm run build -w create-apiagex
|
|
54
|
+
cd newproject
|
|
55
|
+
npx create-apiagex my-cms --yes
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
After scaffolding:
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
cd my-cms
|
|
62
|
+
npm install
|
|
63
|
+
npm run dev
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
## Hinglish
|
|
67
|
+
|
|
68
|
+
`create-apiagex` new Apiagex project start karne ke liye installer CLI hai.
|
|
69
|
+
|
|
70
|
+
Current CLI behavior:
|
|
71
|
+
|
|
72
|
+
- `create-apiagex --help` se help print hoti hai.
|
|
73
|
+
- `create-apiagex --version` se version print hota hai.
|
|
74
|
+
- Interactive terminal me project name aur setup choices puche jaate hain.
|
|
75
|
+
- CI/non-interactive scaffold ke liye `--yes` aur setup flags support hain.
|
|
76
|
+
- Target folder safe slug hona chahiye, jaise `my-cms`.
|
|
77
|
+
- Existing non-empty folder overwrite nahi hota.
|
|
78
|
+
- `--dry-run` scaffold plan dikhata hai bina files likhe.
|
|
79
|
+
- Target folder missing ya empty ho to small starter scaffold create hota hai.
|
|
80
|
+
|
|
81
|
+
Interactive setup setup mode, package manager, dependency install preference, git init preference, aur owner bootstrap preference puchta hai.
|
|
82
|
+
|
|
83
|
+
Generated starter files:
|
|
84
|
+
|
|
85
|
+
- `package.json`
|
|
86
|
+
- `.gitignore`
|
|
87
|
+
- `.env.example`
|
|
88
|
+
- `apiagex.config.json`
|
|
89
|
+
- `README.md`
|
|
90
|
+
- `docs/README.md`
|
|
91
|
+
|
|
92
|
+
Generated `.env.example` local persistence ke liye `APIAGEX_DATABASE_PATH=.apiagex/apiagex.sqlite` aur `APIAGEX_UPLOADS_PATH=.apiagex/uploads` document karta hai.
|
|
93
|
+
|
|
94
|
+
Generated `package.json` `apiagex-server` par depend karta hai aur `npm run dev`, `npm run start`, `npm run smoke`, aur `npm run build` expose karta hai.
|
|
95
|
+
|
|
96
|
+
Generated starter README users ko `/doc`, `/readme`, aur `/adminui` par point karta hai, practical owner/schema/entry/role/webhook/realtime flow include karta hai, aur common errors explain karta hai.
|
|
97
|
+
|
|
98
|
+
Generated `docs/README.md` generated API shape, access control, webhooks, realtime, relation docs, payloads, populate options, Admin UI entry pickers, aur common errors explain karta hai.
|
|
99
|
+
|
|
100
|
+
Generated-project test network install ke bina scaffold verify karta hai: temporary project create karta hai, runtime smoke command chalata hai, temporary port/database par Apiagex start karta hai, aur `/api/health`, `/adminui`, `/doc`, plus `/readme` check karta hai.
|
|
101
|
+
|
|
102
|
+
Example:
|
|
103
|
+
|
|
104
|
+
```bash
|
|
105
|
+
npm create apiagex@latest my-cms
|
|
106
|
+
npx create-apiagex my-cms
|
|
107
|
+
npm run build -w create-apiagex
|
|
108
|
+
node packages/create-apiagex/dist/index.js my-cms --dry-run
|
|
109
|
+
node packages/create-apiagex/dist/index.js my-cms --yes
|
|
110
|
+
node packages/create-apiagex/dist/index.js --dry-run
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
Publish se pehle local workspace test:
|
|
114
|
+
|
|
115
|
+
```bash
|
|
116
|
+
npm run build -w create-apiagex
|
|
117
|
+
cd newproject
|
|
118
|
+
npx create-apiagex my-cms --yes
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
Scaffold ke baad:
|
|
122
|
+
|
|
123
|
+
```bash
|
|
124
|
+
cd my-cms
|
|
125
|
+
npm install
|
|
126
|
+
npm run dev
|
|
127
|
+
```
|
package/dist/args.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"args.d.ts","sourceRoot":"","sources":["../src/args.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAA6B,MAAM,0BAA0B,CAAC;AAEtF,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,UAAU,GAAG,MAAM,CA6B7D;AAED,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAKtE"}
|
package/dist/args.js
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
export function parseArgs(args) {
|
|
2
|
+
const options = { dryRun: false, help: false, version: false, yes: false };
|
|
3
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
4
|
+
const arg = args[index] ?? "";
|
|
5
|
+
if (arg === "--help" || arg === "-h")
|
|
6
|
+
options.help = true;
|
|
7
|
+
else if (arg === "--version" || arg === "-v")
|
|
8
|
+
options.version = true;
|
|
9
|
+
else if (arg === "--dry-run")
|
|
10
|
+
options.dryRun = true;
|
|
11
|
+
else if (arg === "--yes" || arg === "-y")
|
|
12
|
+
options.yes = true;
|
|
13
|
+
else if (arg === "--install")
|
|
14
|
+
options.installDependencies = true;
|
|
15
|
+
else if (arg === "--no-install")
|
|
16
|
+
options.installDependencies = false;
|
|
17
|
+
else if (arg === "--git")
|
|
18
|
+
options.initGit = true;
|
|
19
|
+
else if (arg === "--no-git")
|
|
20
|
+
options.initGit = false;
|
|
21
|
+
else if (arg === "--owner")
|
|
22
|
+
options.bootstrapOwner = true;
|
|
23
|
+
else if (arg === "--no-owner")
|
|
24
|
+
options.bootstrapOwner = false;
|
|
25
|
+
else if (arg === "--package-manager") {
|
|
26
|
+
const value = args[index + 1];
|
|
27
|
+
if (!isPackageManager(value))
|
|
28
|
+
return "Use --package-manager npm, pnpm, or yarn.";
|
|
29
|
+
options.packageManager = value;
|
|
30
|
+
index += 1;
|
|
31
|
+
}
|
|
32
|
+
else if (arg === "--setup") {
|
|
33
|
+
const value = args[index + 1];
|
|
34
|
+
if (!isSetupMode(value))
|
|
35
|
+
return "Use --setup quickstart or custom.";
|
|
36
|
+
options.setupMode = value;
|
|
37
|
+
index += 1;
|
|
38
|
+
}
|
|
39
|
+
else if (arg.startsWith("-"))
|
|
40
|
+
return `Unknown option: ${arg}`;
|
|
41
|
+
else if (options.target)
|
|
42
|
+
return "Only one target folder is supported.";
|
|
43
|
+
else
|
|
44
|
+
options.target = arg;
|
|
45
|
+
}
|
|
46
|
+
return options;
|
|
47
|
+
}
|
|
48
|
+
export function validateProjectSlug(target) {
|
|
49
|
+
if (!/^[a-z][a-z0-9-]*$/.test(target))
|
|
50
|
+
return "Target folder must be a safe slug like my-cms.";
|
|
51
|
+
if (target.includes("--"))
|
|
52
|
+
return "Target folder cannot contain repeated hyphens.";
|
|
53
|
+
if (target.endsWith("-"))
|
|
54
|
+
return "Target folder cannot end with a hyphen.";
|
|
55
|
+
return undefined;
|
|
56
|
+
}
|
|
57
|
+
function isPackageManager(value) {
|
|
58
|
+
return value === "npm" || value === "pnpm" || value === "yarn";
|
|
59
|
+
}
|
|
60
|
+
function isSetupMode(value) {
|
|
61
|
+
return value === "quickstart" || value === "custom";
|
|
62
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import type { Readable, Writable } from "node:stream";
|
|
2
|
+
export type PackageManager = "npm" | "pnpm" | "yarn";
|
|
3
|
+
export type SetupMode = "custom" | "quickstart";
|
|
4
|
+
export type CliOptions = {
|
|
5
|
+
bootstrapOwner?: boolean;
|
|
6
|
+
dryRun: boolean;
|
|
7
|
+
help: boolean;
|
|
8
|
+
initGit?: boolean;
|
|
9
|
+
installDependencies?: boolean;
|
|
10
|
+
packageManager?: PackageManager;
|
|
11
|
+
setupMode?: SetupMode;
|
|
12
|
+
target?: string;
|
|
13
|
+
version: boolean;
|
|
14
|
+
yes: boolean;
|
|
15
|
+
};
|
|
16
|
+
export type CliResult = {
|
|
17
|
+
code: number;
|
|
18
|
+
stderr: string;
|
|
19
|
+
stdout: string;
|
|
20
|
+
};
|
|
21
|
+
export type PromptQuestion = {
|
|
22
|
+
defaultValue: string;
|
|
23
|
+
message: string;
|
|
24
|
+
};
|
|
25
|
+
export type PromptFn = (question: PromptQuestion) => Promise<string>;
|
|
26
|
+
export type RunCliOptions = {
|
|
27
|
+
interactive?: boolean;
|
|
28
|
+
prompt?: PromptFn;
|
|
29
|
+
stdin?: Readable;
|
|
30
|
+
stdout?: Writable;
|
|
31
|
+
};
|
|
32
|
+
export type ScaffoldAnswers = {
|
|
33
|
+
bootstrapOwner: boolean;
|
|
34
|
+
initGit: boolean;
|
|
35
|
+
installDependencies: boolean;
|
|
36
|
+
packageManager: PackageManager;
|
|
37
|
+
setupMode: SetupMode;
|
|
38
|
+
target: string;
|
|
39
|
+
};
|
|
40
|
+
export type ScaffoldFile = {
|
|
41
|
+
content: string;
|
|
42
|
+
path: string;
|
|
43
|
+
};
|
|
44
|
+
//# sourceMappingURL=create-apiagex.type.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"create-apiagex.type.d.ts","sourceRoot":"","sources":["../src/create-apiagex.type.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAEtD,MAAM,MAAM,cAAc,GAAG,KAAK,GAAG,MAAM,GAAG,MAAM,CAAC;AACrD,MAAM,MAAM,SAAS,GAAG,QAAQ,GAAG,YAAY,CAAC;AAEhD,MAAM,MAAM,UAAU,GAAG;IACvB,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,MAAM,EAAE,OAAO,CAAC;IAChB,IAAI,EAAE,OAAO,CAAC;IACd,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,mBAAmB,CAAC,EAAE,OAAO,CAAC;IAC9B,cAAc,CAAC,EAAE,cAAc,CAAC;IAChC,SAAS,CAAC,EAAE,SAAS,CAAC;IACtB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,OAAO,CAAC;IACjB,GAAG,EAAE,OAAO,CAAC;CACd,CAAC;AAEF,MAAM,MAAM,SAAS,GAAG;IACtB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;CAChB,CAAC;AAEF,MAAM,MAAM,cAAc,GAAG;IAC3B,YAAY,EAAE,MAAM,CAAC;IACrB,OAAO,EAAE,MAAM,CAAC;CACjB,CAAC;AAEF,MAAM,MAAM,QAAQ,GAAG,CAAC,QAAQ,EAAE,cAAc,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;AAErE,MAAM,MAAM,aAAa,GAAG;IAC1B,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,MAAM,CAAC,EAAE,QAAQ,CAAC;IAClB,KAAK,CAAC,EAAE,QAAQ,CAAC;IACjB,MAAM,CAAC,EAAE,QAAQ,CAAC;CACnB,CAAC;AAEF,MAAM,MAAM,eAAe,GAAG;IAC5B,cAAc,EAAE,OAAO,CAAC;IACxB,OAAO,EAAE,OAAO,CAAC;IACjB,mBAAmB,EAAE,OAAO,CAAC;IAC7B,cAAc,EAAE,cAAc,CAAC;IAC/B,SAAS,EAAE,SAAS,CAAC;IACrB,MAAM,EAAE,MAAM,CAAC;CAChB,CAAC;AAEF,MAAM,MAAM,YAAY,GAAG;IACzB,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;CACd,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AASA,OAAO,KAAK,EAAE,SAAS,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAC;AAIzE,wBAAsB,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,GAAG,SAAgB,EAAE,EAAE,GAAE,aAAkB,GAAG,OAAO,CAAC,SAAS,CAAC,CA4B5G"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { existsSync } from "node:fs";
|
|
3
|
+
import { realpathSync } from "node:fs";
|
|
4
|
+
import { mkdir, readdir, writeFile } from "node:fs/promises";
|
|
5
|
+
import { basename, dirname, join, resolve } from "node:path";
|
|
6
|
+
import { fileURLToPath } from "node:url";
|
|
7
|
+
import { parseArgs, validateProjectSlug } from "./args.js";
|
|
8
|
+
import { resolveAnswers } from "./prompts.js";
|
|
9
|
+
import { createScaffoldFiles, renderPlan } from "./scaffold.js";
|
|
10
|
+
const packageVersion = "0.6.1";
|
|
11
|
+
export async function runCli(args, cwd = process.cwd(), io = {}) {
|
|
12
|
+
const parsed = parseArgs(args);
|
|
13
|
+
if (typeof parsed === "string")
|
|
14
|
+
return fail(parsed);
|
|
15
|
+
if (parsed.help)
|
|
16
|
+
return ok(renderHelp());
|
|
17
|
+
if (parsed.version)
|
|
18
|
+
return ok(`create-apiagex ${packageVersion}\n`);
|
|
19
|
+
const answers = await resolveAnswers(parsed, io);
|
|
20
|
+
if (typeof answers === "string")
|
|
21
|
+
return fail(answers);
|
|
22
|
+
const validationError = validateProjectSlug(answers.target);
|
|
23
|
+
if (validationError)
|
|
24
|
+
return fail(validationError);
|
|
25
|
+
const targetDir = resolve(cwd, answers.target);
|
|
26
|
+
const folderState = await inspectTargetFolder(targetDir);
|
|
27
|
+
if (folderState === "non-empty")
|
|
28
|
+
return fail(`Refusing to overwrite non-empty folder: ${targetDir}`);
|
|
29
|
+
const projectName = basename(targetDir);
|
|
30
|
+
const files = createScaffoldFiles({ ...answers, target: projectName });
|
|
31
|
+
const plan = renderPlan(projectName, targetDir, files, answers, parsed.dryRun);
|
|
32
|
+
if (parsed.dryRun)
|
|
33
|
+
return ok(plan);
|
|
34
|
+
await mkdir(targetDir, { recursive: true });
|
|
35
|
+
for (const file of files) {
|
|
36
|
+
const filePath = join(targetDir, file.path);
|
|
37
|
+
await mkdir(dirname(filePath), { recursive: true });
|
|
38
|
+
await writeFile(filePath, file.content, "utf8");
|
|
39
|
+
}
|
|
40
|
+
return ok(`${plan}\nCreated ${files.length} files.\n`);
|
|
41
|
+
}
|
|
42
|
+
async function inspectTargetFolder(targetDir) {
|
|
43
|
+
if (!existsSync(targetDir))
|
|
44
|
+
return "missing";
|
|
45
|
+
const entries = await readdir(targetDir);
|
|
46
|
+
return entries.length === 0 ? "empty" : "non-empty";
|
|
47
|
+
}
|
|
48
|
+
function renderHelp() {
|
|
49
|
+
return `create-apiagex ${packageVersion}
|
|
50
|
+
|
|
51
|
+
Usage:
|
|
52
|
+
create-apiagex [target-folder] [options]
|
|
53
|
+
|
|
54
|
+
Options:
|
|
55
|
+
--setup quickstart|custom Choose starter setup mode.
|
|
56
|
+
--package-manager npm|pnpm|yarn
|
|
57
|
+
--install, --no-install Record whether dependencies should be installed after scaffold.
|
|
58
|
+
--git, --no-git Record whether git should be initialized after scaffold.
|
|
59
|
+
--owner, --no-owner Record whether owner setup should happen now or in Admin UI.
|
|
60
|
+
--dry-run Print the scaffold plan without writing files.
|
|
61
|
+
-y, --yes Use defaults for missing options.
|
|
62
|
+
-h, --help Show help.
|
|
63
|
+
-v, --version Show version.
|
|
64
|
+
|
|
65
|
+
Rules:
|
|
66
|
+
Target folder must be a safe slug like my-cms.
|
|
67
|
+
Existing non-empty folders are never overwritten.
|
|
68
|
+
`;
|
|
69
|
+
}
|
|
70
|
+
function ok(stdout) {
|
|
71
|
+
return { code: 0, stdout, stderr: "" };
|
|
72
|
+
}
|
|
73
|
+
function fail(message) {
|
|
74
|
+
return { code: 1, stdout: "", stderr: `${message}\n` };
|
|
75
|
+
}
|
|
76
|
+
if (isDirectRun()) {
|
|
77
|
+
const result = await runCli(process.argv.slice(2), process.cwd(), { interactive: process.stdin.isTTY });
|
|
78
|
+
if (result.stdout)
|
|
79
|
+
process.stdout.write(result.stdout);
|
|
80
|
+
if (result.stderr)
|
|
81
|
+
process.stderr.write(result.stderr);
|
|
82
|
+
process.exitCode = result.code;
|
|
83
|
+
}
|
|
84
|
+
function isDirectRun() {
|
|
85
|
+
const entry = process.argv[1];
|
|
86
|
+
return Boolean(entry && realpathSync(entry) === fileURLToPath(import.meta.url));
|
|
87
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"prompts.d.ts","sourceRoot":"","sources":["../src/prompts.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,UAAU,EAEV,aAAa,EACb,eAAe,EAChB,MAAM,0BAA0B,CAAC;AAElC,wBAAsB,cAAc,CAAC,OAAO,EAAE,UAAU,EAAE,EAAE,GAAE,aAAkB,GAAG,OAAO,CAAC,eAAe,GAAG,MAAM,CAAC,CAenH"}
|
package/dist/prompts.js
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { createInterface } from "node:readline/promises";
|
|
2
|
+
export async function resolveAnswers(options, io = {}) {
|
|
3
|
+
const prompt = io.prompt ?? nodePrompt(io);
|
|
4
|
+
const canAsk = Boolean((io.interactive || io.prompt) && !options.yes);
|
|
5
|
+
const target = options.target ?? (canAsk ? await ask(prompt, "Project name", "my-apiagex") : undefined);
|
|
6
|
+
if (!target)
|
|
7
|
+
return "Target folder is required. Run create-apiagex --help for usage.";
|
|
8
|
+
const setupMode = options.setupMode ?? await choiceAnswer(prompt, canAsk, "Setup mode", "quickstart", ["quickstart", "custom"]);
|
|
9
|
+
const packageManager = options.packageManager ?? await choiceAnswer(prompt, canAsk, "Package manager", "npm", ["npm", "pnpm", "yarn"]);
|
|
10
|
+
return {
|
|
11
|
+
bootstrapOwner: await boolAnswer(prompt, canAsk, "Bootstrap owner during scaffold?", options.bootstrapOwner, false),
|
|
12
|
+
initGit: await boolAnswer(prompt, canAsk, "Initialize git repository?", options.initGit, true),
|
|
13
|
+
installDependencies: await boolAnswer(prompt, canAsk, "Install dependencies after scaffold?", options.installDependencies, false),
|
|
14
|
+
packageManager,
|
|
15
|
+
setupMode,
|
|
16
|
+
target,
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
async function boolAnswer(prompt, canAsk, message, value, defaultValue) {
|
|
20
|
+
if (value !== undefined || !canAsk)
|
|
21
|
+
return value ?? defaultValue;
|
|
22
|
+
const answer = (await ask(prompt, message, defaultValue ? "yes" : "no")).toLowerCase();
|
|
23
|
+
return answer === "y" || answer === "yes" || answer === "true";
|
|
24
|
+
}
|
|
25
|
+
async function choiceAnswer(prompt, canAsk, message, defaultValue, allowed) {
|
|
26
|
+
if (!canAsk)
|
|
27
|
+
return defaultValue;
|
|
28
|
+
const answer = await ask(prompt, `${message} (${allowed.join("/")})`, defaultValue);
|
|
29
|
+
return allowed.includes(answer) ? answer : defaultValue;
|
|
30
|
+
}
|
|
31
|
+
async function ask(prompt, message, defaultValue) {
|
|
32
|
+
const answer = (await prompt({ defaultValue, message })).trim();
|
|
33
|
+
return answer || defaultValue;
|
|
34
|
+
}
|
|
35
|
+
function nodePrompt(io) {
|
|
36
|
+
return async ({ defaultValue, message }) => {
|
|
37
|
+
const input = io.stdin ?? process.stdin;
|
|
38
|
+
const output = io.stdout ?? process.stdout;
|
|
39
|
+
const rl = createInterface({ input, output });
|
|
40
|
+
try {
|
|
41
|
+
return await rl.question(`${message} (${defaultValue}): `);
|
|
42
|
+
}
|
|
43
|
+
finally {
|
|
44
|
+
rl.close();
|
|
45
|
+
}
|
|
46
|
+
};
|
|
47
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { ScaffoldAnswers, ScaffoldFile } from "./create-apiagex.type.js";
|
|
2
|
+
export declare function createScaffoldFiles(answers: ScaffoldAnswers): ScaffoldFile[];
|
|
3
|
+
export declare function renderPlan(projectName: string, targetDir: string, files: ScaffoldFile[], answers: ScaffoldAnswers, dryRun: boolean): string;
|
|
4
|
+
//# sourceMappingURL=scaffold.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scaffold.d.ts","sourceRoot":"","sources":["../src/scaffold.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAC;AAE9E,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,eAAe,GAAG,YAAY,EAAE,CAoD5E;AAED,wBAAgB,UAAU,CAAC,WAAW,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,YAAY,EAAE,EAAE,OAAO,EAAE,eAAe,EAAE,MAAM,EAAE,OAAO,GAAG,MAAM,CAuB3I"}
|
package/dist/scaffold.js
ADDED
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
export function createScaffoldFiles(answers) {
|
|
2
|
+
return [
|
|
3
|
+
{
|
|
4
|
+
path: "package.json",
|
|
5
|
+
content: `${JSON.stringify({
|
|
6
|
+
name: answers.target,
|
|
7
|
+
version: "0.1.0",
|
|
8
|
+
private: true,
|
|
9
|
+
type: "module",
|
|
10
|
+
scripts: {
|
|
11
|
+
dev: "apiagex dev",
|
|
12
|
+
start: "apiagex start",
|
|
13
|
+
build: "apiagex build",
|
|
14
|
+
smoke: "apiagex smoke",
|
|
15
|
+
},
|
|
16
|
+
dependencies: {
|
|
17
|
+
"apiagex-server": "^0.6.1",
|
|
18
|
+
},
|
|
19
|
+
}, null, 2)}\n`,
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
path: "README.md",
|
|
23
|
+
content: starterReadme(answers),
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
path: ".gitignore",
|
|
27
|
+
content: "node_modules\n.env\ndist\n.apiagex\n",
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
path: ".env.example",
|
|
31
|
+
content: "APIAGEX_DATABASE_PATH=.apiagex/apiagex.sqlite\nAPIAGEX_UPLOADS_PATH=.apiagex/uploads\nPORT=4000\nHOST=127.0.0.1\n",
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
path: "apiagex.config.json",
|
|
35
|
+
content: `${JSON.stringify({
|
|
36
|
+
database: { provider: "sqlite", url: "file:.apiagex/apiagex.sqlite" },
|
|
37
|
+
project: { packageManager: answers.packageManager, setupMode: answers.setupMode },
|
|
38
|
+
}, null, 2)}\n`,
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
path: "docs/README.md",
|
|
42
|
+
content: docsReadme(),
|
|
43
|
+
},
|
|
44
|
+
];
|
|
45
|
+
}
|
|
46
|
+
export function renderPlan(projectName, targetDir, files, answers, dryRun) {
|
|
47
|
+
const mode = dryRun ? "Dry run only. No files were written." : "Scaffolding project files.";
|
|
48
|
+
const fileList = files.map((file) => `- ${file.path}`).join("\n");
|
|
49
|
+
return [
|
|
50
|
+
`create-apiagex will create ${projectName} at ${targetDir}.`,
|
|
51
|
+
mode,
|
|
52
|
+
"",
|
|
53
|
+
"Selected setup:",
|
|
54
|
+
`- Setup mode: ${answers.setupMode}`,
|
|
55
|
+
`- Package manager: ${answers.packageManager}`,
|
|
56
|
+
`- Install dependencies: ${answers.installDependencies ? "yes" : "no"}`,
|
|
57
|
+
`- Initialize git: ${answers.initGit ? "yes" : "no"}`,
|
|
58
|
+
`- Owner setup: ${answers.bootstrapOwner ? "create now" : "create from Admin UI"}`,
|
|
59
|
+
"",
|
|
60
|
+
"Files:",
|
|
61
|
+
fileList,
|
|
62
|
+
"",
|
|
63
|
+
"Next commands:",
|
|
64
|
+
`cd ${projectName}`,
|
|
65
|
+
installCommand(answers.packageManager),
|
|
66
|
+
runCommand(answers.packageManager, "dev"),
|
|
67
|
+
"",
|
|
68
|
+
].join("\n");
|
|
69
|
+
}
|
|
70
|
+
function starterReadme(answers) {
|
|
71
|
+
return `# ${answers.target}
|
|
72
|
+
|
|
73
|
+
Generated Apiagex starter.
|
|
74
|
+
|
|
75
|
+
## What this project gives you
|
|
76
|
+
|
|
77
|
+
English: Apiagex runs one server with /api, /adminui, /doc, and /readme. Use the Admin UI to create schemas, entries, API roles, users, webhooks, and realtime settings.
|
|
78
|
+
|
|
79
|
+
Hinglish: Apiagex ek server chalata hai jisme /api, /adminui, /doc, aur /readme hote hain. Schemas, entries, API roles, users, webhooks, aur realtime settings ke liye Admin UI use karo.
|
|
80
|
+
|
|
81
|
+
## Next commands
|
|
82
|
+
|
|
83
|
+
\`\`\`bash
|
|
84
|
+
${installCommand(answers.packageManager)}
|
|
85
|
+
${runCommand(answers.packageManager, "dev")}
|
|
86
|
+
\`\`\`
|
|
87
|
+
|
|
88
|
+
Open http://127.0.0.1:4000/adminui to create the first owner. Open /doc for API docs and /readme for the readable project summary.
|
|
89
|
+
|
|
90
|
+
## Scripts
|
|
91
|
+
|
|
92
|
+
- \`${runCommand(answers.packageManager, "dev")}\`: start the local Apiagex server.
|
|
93
|
+
- \`${runCommand(answers.packageManager, "start")}\`: start the server for regular runtime use.
|
|
94
|
+
- \`${runCommand(answers.packageManager, "smoke")}\`: verify the runtime health route.
|
|
95
|
+
- \`${runCommand(answers.packageManager, "build")}\`: print runtime build guidance.
|
|
96
|
+
|
|
97
|
+
## Environment
|
|
98
|
+
|
|
99
|
+
Copy .env.example to .env if you need custom paths.
|
|
100
|
+
|
|
101
|
+
- APIAGEX_DATABASE_PATH: SQLite database path. Default .apiagex/apiagex.sqlite.
|
|
102
|
+
- APIAGEX_UPLOADS_PATH: upload folder. Default .apiagex/uploads.
|
|
103
|
+
- PORT: server port. Default 4000.
|
|
104
|
+
- HOST: server host. Default 127.0.0.1.
|
|
105
|
+
|
|
106
|
+
## Practical flow
|
|
107
|
+
|
|
108
|
+
English:
|
|
109
|
+
|
|
110
|
+
1. Create the first owner from /adminui.
|
|
111
|
+
2. Create a schema, for example Article with a required title field.
|
|
112
|
+
3. Create entries from Entries or call POST /api/content/article.
|
|
113
|
+
4. Create Content Roles, save permissions, then create users or API tokens.
|
|
114
|
+
5. Use Webhooks for external server notifications and Realtime API for live browser screens.
|
|
115
|
+
|
|
116
|
+
Hinglish:
|
|
117
|
+
|
|
118
|
+
1. /adminui se first owner create karo.
|
|
119
|
+
2. Schema banao, jaise required title field ke saath Article.
|
|
120
|
+
3. Entries screen se entry banao ya POST /api/content/article call karo.
|
|
121
|
+
4. Content Roles banao, permissions save karo, phir users ya API tokens create karo.
|
|
122
|
+
5. External server notifications ke liye Webhooks aur live browser screens ke liye Realtime API use karo.
|
|
123
|
+
|
|
124
|
+
## Common errors
|
|
125
|
+
|
|
126
|
+
- OWNER_ALREADY_BOOTSTRAPPED: owner already exists; use the same form to login.
|
|
127
|
+
- API_PERMISSION_DENIED: content API role is missing the required permission.
|
|
128
|
+
- API_TOKEN_INVALID: token is wrong or revoked.
|
|
129
|
+
- REALTIME_SESSION_INVALID: realtime session token was reused or expired.
|
|
130
|
+
|
|
131
|
+
## Relation Modeling
|
|
132
|
+
|
|
133
|
+
English: Create the target schema first, then add relation fields to the source schema in /adminui. Use one-to-one for Profile to User, many-to-one for Article to Category, one-to-many for Author to Articles, and many-to-many for Articles to Tags.
|
|
134
|
+
|
|
135
|
+
Hinglish: Pehle target schema banao, phir /adminui me source schema par relation fields add karo. Profile to User ke liye one-to-one, Article to Category ke liye many-to-one, Author to Articles ke liye one-to-many, aur Articles to Tags ke liye many-to-many use karo.
|
|
136
|
+
`;
|
|
137
|
+
}
|
|
138
|
+
function docsReadme() {
|
|
139
|
+
return `# Apiagex Project Docs
|
|
140
|
+
|
|
141
|
+
Use /doc for generated API docs and /readme for the project summary.
|
|
142
|
+
|
|
143
|
+
## Owner and Admin UI
|
|
144
|
+
|
|
145
|
+
English: Open /adminui, create the first owner, then use the same page for later logins.
|
|
146
|
+
|
|
147
|
+
Hinglish: /adminui open karo, first owner create karo, phir later login ke liye same page use karo.
|
|
148
|
+
|
|
149
|
+
## Generated APIs
|
|
150
|
+
|
|
151
|
+
English: Every schema creates /api/content/:schemaSlug. Send entry data as { "data": { ... } }.
|
|
152
|
+
|
|
153
|
+
Hinglish: Har schema /api/content/:schemaSlug create karta hai. Entry data { "data": { ... } } shape me bhejo.
|
|
154
|
+
|
|
155
|
+
## Access control
|
|
156
|
+
|
|
157
|
+
English: Content API roles are separate from Admin UI roles. Give getAll, get, create, update, delete, or manage per schema.
|
|
158
|
+
|
|
159
|
+
Hinglish: Content API roles Admin UI roles se alag hain. Har schema ke liye getAll, get, create, update, delete, ya manage do.
|
|
160
|
+
|
|
161
|
+
## Webhooks and Realtime
|
|
162
|
+
|
|
163
|
+
English: Webhooks call external URLs after content changes. Realtime API sends WebSocket events only for enabled collections.
|
|
164
|
+
|
|
165
|
+
Hinglish: Webhooks content change ke baad external URLs call karte hain. Realtime API sirf enabled collections ke liye WebSocket events bhejta hai.
|
|
166
|
+
|
|
167
|
+
Relation docs: /doc explains relation field types, entry payloads, populate query options, Admin UI entry pickers, and common errors.
|
|
168
|
+
`;
|
|
169
|
+
}
|
|
170
|
+
function installCommand(packageManager) {
|
|
171
|
+
return packageManager === "yarn" ? "yarn install" : `${packageManager} install`;
|
|
172
|
+
}
|
|
173
|
+
function runCommand(packageManager, script) {
|
|
174
|
+
return packageManager === "npm" ? `npm run ${script}` : `${packageManager} ${script}`;
|
|
175
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "create-apiagex",
|
|
3
|
+
"version": "0.6.1",
|
|
4
|
+
"description": "Installer CLI for creating Apiagex CMS projects.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "git+https://github.com/bestmaa/apiagex.git",
|
|
10
|
+
"directory": "packages/create-apiagex"
|
|
11
|
+
},
|
|
12
|
+
"bugs": {
|
|
13
|
+
"url": "https://github.com/bestmaa/apiagex/issues"
|
|
14
|
+
},
|
|
15
|
+
"homepage": "https://github.com/bestmaa/apiagex#readme",
|
|
16
|
+
"keywords": [
|
|
17
|
+
"apiagex",
|
|
18
|
+
"create-apiagex",
|
|
19
|
+
"headless-cms",
|
|
20
|
+
"cms",
|
|
21
|
+
"scaffold"
|
|
22
|
+
],
|
|
23
|
+
"publishConfig": {
|
|
24
|
+
"access": "public"
|
|
25
|
+
},
|
|
26
|
+
"files": [
|
|
27
|
+
"dist",
|
|
28
|
+
"README.md",
|
|
29
|
+
"LICENSE"
|
|
30
|
+
],
|
|
31
|
+
"bin": {
|
|
32
|
+
"create-apiagex": "./dist/index.js"
|
|
33
|
+
},
|
|
34
|
+
"exports": {
|
|
35
|
+
".": "./dist/index.js"
|
|
36
|
+
},
|
|
37
|
+
"scripts": {
|
|
38
|
+
"dev": "tsx src/index.ts",
|
|
39
|
+
"build": "tsc -b && node ../../scripts/set-bin-permissions.mjs dist/index.js",
|
|
40
|
+
"test": "vitest run"
|
|
41
|
+
},
|
|
42
|
+
"devDependencies": {
|
|
43
|
+
"tsx": "^4.21.0"
|
|
44
|
+
}
|
|
45
|
+
}
|