create-du-app 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/README.md ADDED
@@ -0,0 +1,28 @@
1
+ # @company-starter/cli (`create-app`)
2
+
3
+ CLI generator: hỏi tên project + chọn template (Mobile / Frontend / Backend) → sinh ra
4
+ một monorepo sản phẩm độc lập.
5
+
6
+ ## Chạy
7
+
8
+ ```bash
9
+ cd packages/cli
10
+ pnpm install
11
+ pnpm link --global # đăng ký lệnh global `create-app`
12
+ create-app # hoặc: node src/index.js
13
+ ```
14
+
15
+ ## Source
16
+
17
+ | File | Vai trò |
18
+ |---------------|---------|
19
+ | `index.js` | entry point, parse args (`--help`, `--version`), gọi prompt rồi generate |
20
+ | `registry.js` | **khai báo dạng data**: các nhóm + tech + `templatePath` + `lang`. Thêm/bớt template chỉ sửa file này |
21
+ | `prompts.js` | logic hỏi tương tác (`@clack/prompts`): tên project → multiselect nhóm → select tech mỗi nhóm |
22
+ | `generate.js` | copy template, rename `_package.json`→`package.json`, sinh `package.json` + `pnpm-workspace.yaml` gốc động, tạo `packages/shared` có điều kiện, thay `{{PROJECT_NAME}}` |
23
+
24
+ ## Quy ước template
25
+
26
+ - File đặt tên `_package.json` → generate thành `package.json`. Tương tự `_gitignore` → `.gitignore`, `_npmrc` → `.npmrc`.
27
+ - Mọi file text chứa `{{PROJECT_NAME}}` sẽ được thay bằng tên project thật.
28
+ - `packages/shared` chỉ được tạo khi có **>= 2** lựa chọn `lang === 'js'`.
package/package.json ADDED
@@ -0,0 +1,26 @@
1
+ {
2
+ "name": "create-du-app",
3
+ "version": "0.1.0",
4
+ "description": "CLI generator: chọn template (Mobile/FE/BE) và sinh ra monorepo sản phẩm",
5
+ "type": "module",
6
+ "bin": {
7
+ "create-app": "src/index.js"
8
+ },
9
+ "files": [
10
+ "src",
11
+ "templates"
12
+ ],
13
+ "scripts": {
14
+ "start": "node src/index.js",
15
+ "prepack": "node scripts/bundle-templates.mjs",
16
+ "postpack": "node scripts/bundle-templates.mjs --clean"
17
+ },
18
+ "engines": {
19
+ "node": ">=18"
20
+ },
21
+ "packageManager": "pnpm@9.0.0",
22
+ "dependencies": {
23
+ "@clack/prompts": "^0.7.0",
24
+ "fs-extra": "^11.2.0"
25
+ }
26
+ }
@@ -0,0 +1,133 @@
1
+ // generate.js — copy template + rename + sinh package.json động + replace placeholder.
2
+ //
3
+ // Đầu vào: plan từ prompts.js → { projectName, selections: [{group, option}] }
4
+ // và `repoRoot` (root của company-starter, nơi chứa templates/).
5
+
6
+ import path from 'node:path';
7
+ import fse from 'fs-extra';
8
+ import {
9
+ SHARED_TEMPLATE_PATH,
10
+ SHARED_OUTPUT_DIR,
11
+ } from './registry.js';
12
+
13
+ const PLACEHOLDER = /\{\{PROJECT_NAME\}\}/g;
14
+
15
+ // File trong template được đặt tên "an toàn" để npm/tooling repo gốc không quét nhầm.
16
+ // Khi generate ra sản phẩm thật thì rename lại tên chuẩn.
17
+ const RENAME_MAP = {
18
+ '_package.json': 'package.json',
19
+ '_gitignore': '.gitignore',
20
+ '_npmrc': '.npmrc',
21
+ };
22
+
23
+ // Phần mở rộng được coi là "text" → sẽ thay {{PROJECT_NAME}}.
24
+ const TEXT_EXT = new Set([
25
+ '.json', '.js', '.jsx', '.ts', '.tsx', '.md', '.txt', '.yml', '.yaml',
26
+ '.env', '.html', '.css', '.scss', '.gitignore', '.npmrc', '.dart', '.php',
27
+ ]);
28
+
29
+ function isTextFile(filePath) {
30
+ const base = path.basename(filePath);
31
+ if (base === '.gitignore' || base === '.npmrc' || base === '.env') return true;
32
+ return TEXT_EXT.has(path.extname(filePath).toLowerCase());
33
+ }
34
+
35
+ function renameBasename(name) {
36
+ return RENAME_MAP[name] ?? name;
37
+ }
38
+
39
+ // Copy 1 template folder → đích, vừa rename file vừa replace {{PROJECT_NAME}}.
40
+ async function copyTemplate(srcDir, destDir, projectName) {
41
+ // Template rỗng (chưa bỏ source thật vào) vẫn phải chạy được.
42
+ if (!(await fse.pathExists(srcDir))) {
43
+ throw new Error(`Không tìm thấy template: ${srcDir}`);
44
+ }
45
+
46
+ await fse.ensureDir(destDir);
47
+
48
+ const entries = await fse.readdir(srcDir, { withFileTypes: true });
49
+ for (const entry of entries) {
50
+ const srcPath = path.join(srcDir, entry.name);
51
+
52
+ if (entry.isDirectory()) {
53
+ await copyTemplate(srcPath, path.join(destDir, entry.name), projectName);
54
+ continue;
55
+ }
56
+
57
+ const destName = renameBasename(entry.name);
58
+ const destPath = path.join(destDir, destName);
59
+
60
+ if (isTextFile(destPath)) {
61
+ const raw = await fse.readFile(srcPath, 'utf8');
62
+ await fse.writeFile(destPath, raw.replace(PLACEHOLDER, projectName));
63
+ } else {
64
+ await fse.copy(srcPath, destPath);
65
+ }
66
+ }
67
+ }
68
+
69
+ // Sinh package.json gốc của project động (pnpm: KHÔNG có field "workspaces").
70
+ function buildRootPackageJson(projectName) {
71
+ return {
72
+ name: projectName,
73
+ version: '0.1.0',
74
+ private: true,
75
+ engines: { node: '>=18' },
76
+ packageManager: 'pnpm@9.0.0',
77
+ };
78
+ }
79
+
80
+ // Sinh nội dung pnpm-workspace.yaml.
81
+ // packages: chỉ các apps/* thực sự tạo + luôn có packages/*.
82
+ function buildPnpmWorkspaceYaml(createdAppDirs) {
83
+ const entries = [...createdAppDirs, 'packages/*'];
84
+ const lines = entries.map((p) => ` - '${p}'`);
85
+ return `packages:\n${lines.join('\n')}\n`;
86
+ }
87
+
88
+ export async function generate(plan, repoRoot, cwd = process.cwd()) {
89
+ const { projectName, selections } = plan;
90
+ const projectRoot = path.resolve(cwd, projectName);
91
+ const logs = [];
92
+
93
+ if (await fse.pathExists(projectRoot)) {
94
+ throw new Error(`Thư mục "${projectName}" đã tồn tại tại ${projectRoot}.`);
95
+ }
96
+
97
+ await fse.ensureDir(projectRoot);
98
+
99
+ // 1) Copy từng app đã chọn.
100
+ const createdAppDirs = [];
101
+ for (const { group, option } of selections) {
102
+ const srcDir = path.resolve(repoRoot, option.templatePath);
103
+ const destDir = path.resolve(projectRoot, group.outputDir);
104
+ await copyTemplate(srcDir, destDir, projectName);
105
+ createdAppDirs.push(group.outputDir);
106
+ logs.push(`✓ ${group.label} (${option.label}) → ${group.outputDir}`);
107
+ }
108
+
109
+ // 2) packages/shared CÓ ĐIỀU KIỆN: >= 2 lựa chọn lang === 'js'.
110
+ const jsCount = selections.filter((s) => s.option.lang === 'js').length;
111
+ if (jsCount >= 2) {
112
+ const srcDir = path.resolve(repoRoot, SHARED_TEMPLATE_PATH);
113
+ const destDir = path.resolve(projectRoot, SHARED_OUTPUT_DIR);
114
+ await copyTemplate(srcDir, destDir, projectName);
115
+ logs.push(`✓ shared → ${SHARED_OUTPUT_DIR} (có ${jsCount} app JS/TS)`);
116
+ } else {
117
+ logs.push(`• Bỏ qua shared vì không đủ app JS/TS (cần >= 2, hiện có ${jsCount}).`);
118
+ }
119
+
120
+ // 3) Sinh package.json gốc + pnpm-workspace.yaml động.
121
+ const rootPkg = buildRootPackageJson(projectName);
122
+ await fse.writeJson(path.join(projectRoot, 'package.json'), rootPkg, { spaces: 2 });
123
+ logs.push('✓ package.json');
124
+
125
+ const wsEntries = [...createdAppDirs, 'packages/*'];
126
+ await fse.writeFile(
127
+ path.join(projectRoot, 'pnpm-workspace.yaml'),
128
+ buildPnpmWorkspaceYaml(createdAppDirs),
129
+ );
130
+ logs.push(`✓ pnpm-workspace.yaml (packages: ${wsEntries.join(', ')})`);
131
+
132
+ return { projectRoot, logs };
133
+ }
package/src/index.js ADDED
@@ -0,0 +1,180 @@
1
+ #!/usr/bin/env node
2
+ // index.js — entry point. Parse args + chạy prompt (TTY) hoặc generate trực tiếp (flags).
3
+ //
4
+ // Cách chạy:
5
+ // create-app chạy tương tác (cần terminal thật)
6
+ // create-app --name my-shop --be nestjs non-interactive (chạy mọi nơi)
7
+ // node src/index.js
8
+ //
9
+ // Flags:
10
+ // -h, --help in trợ giúp
11
+ // -v, --version in version
12
+ // -n, --name <name> tên project (non-interactive)
13
+ // --mobile <id> rn | expo | flutter
14
+ // --fe <id> nextjs | reactjs
15
+ // --be <id> nodejs | nestjs | php
16
+ // -y, --yes không hỏi, generate luôn (cần --name + >=1 nhóm)
17
+
18
+ import path from 'node:path';
19
+ import { fileURLToPath } from 'node:url';
20
+ import fse from 'fs-extra';
21
+ import { runPrompts, outro } from './prompts.js';
22
+ import { generate } from './generate.js';
23
+ import { GROUPS, findOption } from './registry.js';
24
+
25
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
26
+
27
+ // Tìm thư mục gốc chứa templates/. Hỗ trợ 2 kịch bản:
28
+ // 1) Chạy từ repo (clone): templates ở ../../../templates (root company-starter)
29
+ // 2) Cài qua npm (đã publish): templates được đóng gói NGAY TRONG package CLI,
30
+ // ở packages/cli/templates → ../templates
31
+ // Chọn nơi nào thực sự tồn tại folder templates/.
32
+ function resolveTemplatesRoot() {
33
+ const candidates = [
34
+ path.resolve(__dirname, '..'), // npm publish: cli/templates
35
+ path.resolve(__dirname, '..', '..', '..'), // repo: root/templates
36
+ ];
37
+ for (const root of candidates) {
38
+ if (fse.pathExistsSync(path.join(root, 'templates'))) return root;
39
+ }
40
+ // fallback: repo root (để thông báo lỗi của generate.js đủ rõ)
41
+ return path.resolve(__dirname, '..', '..', '..');
42
+ }
43
+ const REPO_ROOT = resolveTemplatesRoot();
44
+
45
+ // map flag → groupId (để gợi ý lỗi cho gọn)
46
+ const GROUP_FLAGS = { mobile: 'mobile', fe: 'fe', be: 'be' };
47
+
48
+ async function readVersion() {
49
+ try {
50
+ const pkg = await fse.readJson(path.resolve(__dirname, '..', 'package.json'));
51
+ return pkg.version ?? '0.0.0';
52
+ } catch {
53
+ return '0.0.0';
54
+ }
55
+ }
56
+
57
+ function printHelp() {
58
+ console.log(`
59
+ create-app — sinh monorepo sản phẩm từ template công ty
60
+
61
+ Cách dùng:
62
+ create-app chạy tương tác (cần terminal thật)
63
+ create-app --name my-shop --be nestjs non-interactive (chạy mọi nơi)
64
+ node src/index.js tương đương (khi chưa link)
65
+
66
+ Tuỳ chọn:
67
+ -h, --help in trợ giúp này
68
+ -v, --version in version
69
+ -n, --name <name> tên project
70
+ --mobile <id> rn | expo | flutter
71
+ --fe <id> nextjs | reactjs
72
+ --be <id> nodejs | nestjs | php
73
+ -y, --yes generate luôn không hỏi (cần --name + >=1 nhóm)
74
+
75
+ Ví dụ:
76
+ create-app --name my-shop --mobile expo --fe nextjs --be nestjs
77
+
78
+ Thêm/bớt template: sửa packages/cli/src/registry.js
79
+ `);
80
+ }
81
+
82
+ // Parse argv kiểu "--key value" / "-k value" + cờ boolean.
83
+ function parseArgs(argv) {
84
+ const out = { _flags: new Set() };
85
+ for (let i = 0; i < argv.length; i++) {
86
+ const a = argv[i];
87
+ switch (a) {
88
+ case '-h': case '--help': out._flags.add('help'); break;
89
+ case '-v': case '--version': out._flags.add('version'); break;
90
+ case '-y': case '--yes': out._flags.add('yes'); break;
91
+ case '-n': case '--name': out.name = argv[++i]; break;
92
+ case '--mobile': out.mobile = argv[++i]; break;
93
+ case '--fe': out.fe = argv[++i]; break;
94
+ case '--be': out.be = argv[++i]; break;
95
+ default:
96
+ throw new Error(`Tham số không hợp lệ: ${a}. Xem: create-app --help`);
97
+ }
98
+ }
99
+ return out;
100
+ }
101
+
102
+ // Có bất kỳ flag chọn template nào không → đi đường non-interactive.
103
+ function hasSelectionFlags(args) {
104
+ return Boolean(args.name || args.mobile || args.fe || args.be || args._flags.has('yes'));
105
+ }
106
+
107
+ // Dựng plan từ flags, validate từng id theo registry.
108
+ function planFromFlags(args) {
109
+ const name = (args.name ?? '').trim();
110
+ if (!name) {
111
+ throw new Error('Thiếu --name <tên-project>.');
112
+ }
113
+ if (!/^[a-z0-9][a-z0-9-_]*$/i.test(name)) {
114
+ throw new Error('Tên project chỉ gồm chữ/số/-/_ và bắt đầu bằng chữ/số.');
115
+ }
116
+
117
+ const selections = [];
118
+ for (const [flag, groupId] of Object.entries(GROUP_FLAGS)) {
119
+ const optionId = args[flag];
120
+ if (!optionId) continue;
121
+ const found = findOption(groupId, optionId);
122
+ if (!found) {
123
+ const valid = GROUPS.find((g) => g.id === groupId).options.map((o) => o.id).join(', ');
124
+ throw new Error(`--${flag} "${optionId}" không hợp lệ. Hợp lệ: ${valid}.`);
125
+ }
126
+ selections.push(found);
127
+ }
128
+
129
+ if (selections.length === 0) {
130
+ throw new Error('Cần ít nhất 1 nhóm: --mobile / --fe / --be.');
131
+ }
132
+ return { projectName: name, selections };
133
+ }
134
+
135
+ async function main() {
136
+ const args = parseArgs(process.argv.slice(2));
137
+
138
+ if (args._flags.has('help')) {
139
+ printHelp();
140
+ return;
141
+ }
142
+ if (args._flags.has('version')) {
143
+ console.log(await readVersion());
144
+ return;
145
+ }
146
+
147
+ // Quyết định luồng:
148
+ // - có flag chọn template → non-interactive
149
+ // - không có flag NHƯNG stdin không phải TTY → báo lỗi rõ ràng thay vì treo
150
+ // - còn lại → tương tác
151
+ let plan;
152
+ if (hasSelectionFlags(args)) {
153
+ plan = planFromFlags(args);
154
+ } else if (!process.stdin.isTTY) {
155
+ throw new Error(
156
+ 'Terminal hiện tại không hỗ trợ chế độ tương tác (stdin không phải TTY).\n' +
157
+ ' → Chạy trong Terminal.app/iTerm, HOẶC dùng flags:\n' +
158
+ ' create-app --name my-shop --mobile expo --fe nextjs --be nestjs',
159
+ );
160
+ } else {
161
+ plan = await runPrompts();
162
+ }
163
+
164
+ const { projectRoot, logs } = await generate(plan, REPO_ROOT);
165
+
166
+ console.log('');
167
+ for (const line of logs) console.log(' ' + line);
168
+ console.log('');
169
+
170
+ const rel = path.relative(process.cwd(), projectRoot) || projectRoot;
171
+ const msg = `Xong! Tiếp theo:\n\n cd ${rel} && pnpm install\n`;
172
+ // outro() (clack) chỉ đẹp trong TTY; non-interactive thì in thường.
173
+ if (process.stdout.isTTY && !hasSelectionFlags(args)) outro(msg);
174
+ else console.log(msg);
175
+ }
176
+
177
+ main().catch((err) => {
178
+ console.error('\n✗ Lỗi:', err.message);
179
+ process.exit(1);
180
+ });
package/src/prompts.js ADDED
@@ -0,0 +1,74 @@
1
+ // prompts.js — logic hỏi tương tác.
2
+ //
3
+ // Trả về một "plan" thuần data để generate.js thực thi:
4
+ // { projectName, selections: [{ group, option }, ...] }
5
+ // Không làm I/O ra ổ đĩa ở đây — chỉ hỏi và validate.
6
+
7
+ import {
8
+ intro,
9
+ outro,
10
+ text,
11
+ multiselect,
12
+ select,
13
+ isCancel,
14
+ cancel,
15
+ } from '@clack/prompts';
16
+ import { GROUPS, findOption } from './registry.js';
17
+
18
+ function ensureNotCancelled(value) {
19
+ if (isCancel(value)) {
20
+ cancel('Đã huỷ. Không có gì được tạo.');
21
+ process.exit(0);
22
+ }
23
+ return value;
24
+ }
25
+
26
+ export async function runPrompts() {
27
+ intro('create-app — sinh monorepo sản phẩm từ template công ty');
28
+
29
+ // 1) Tên project
30
+ const projectName = ensureNotCancelled(
31
+ await text({
32
+ message: 'Tên project?',
33
+ placeholder: 'my-shop',
34
+ validate(value) {
35
+ const v = (value ?? '').trim();
36
+ if (!v) return 'Tên project không được để trống.';
37
+ if (!/^[a-z0-9][a-z0-9-_]*$/i.test(v)) {
38
+ return 'Chỉ dùng chữ, số, "-" hoặc "_" và bắt đầu bằng chữ/số.';
39
+ }
40
+ return undefined;
41
+ },
42
+ }),
43
+ );
44
+
45
+ // 2) Multi-select các nhóm cần (chọn được nhiều)
46
+ const groupIds = ensureNotCancelled(
47
+ await multiselect({
48
+ message: 'Chọn các nhóm cần (Space để chọn, Enter để xác nhận):',
49
+ options: GROUPS.map((g) => ({ value: g.id, label: g.label })),
50
+ required: true,
51
+ }),
52
+ );
53
+
54
+ // 3) Với mỗi nhóm đã chọn → single-select 1 công nghệ
55
+ const selections = [];
56
+ for (const groupId of groupIds) {
57
+ const group = GROUPS.find((g) => g.id === groupId);
58
+ const optionId = ensureNotCancelled(
59
+ await select({
60
+ message: `${group.label}: chọn 1 công nghệ`,
61
+ options: group.options.map((o) => ({ value: o.id, label: o.label })),
62
+ }),
63
+ );
64
+ const found = findOption(groupId, optionId);
65
+ selections.push(found); // { group, option }
66
+ }
67
+
68
+ return {
69
+ projectName: projectName.trim(),
70
+ selections,
71
+ };
72
+ }
73
+
74
+ export { outro };
@@ -0,0 +1,57 @@
1
+ // registry.js — KHAI BÁO các nhóm + tech + đường dẫn template.
2
+ //
3
+ // Đây là nguồn dữ liệu duy nhất để thêm/bớt template. Muốn thêm một công nghệ mới
4
+ // chỉ cần thêm một object vào `options` của nhóm tương ứng (hoặc thêm hẳn một nhóm).
5
+ // KHÔNG cần đụng tới logic ở prompts.js / generate.js.
6
+ //
7
+ // Mỗi option:
8
+ // id : định danh ngắn (dùng nội bộ)
9
+ // label : nhãn hiển thị khi hỏi
10
+ // templatePath : đường dẫn template, TÍNH TỪ ROOT của repo company-starter này
11
+ // lang : 'js' | 'dart' | 'php' ...
12
+ // Chỉ option có lang === 'js' mới đủ điều kiện dùng packages/shared.
13
+
14
+ export const GROUPS = [
15
+ {
16
+ id: 'mobile',
17
+ label: 'Mobile',
18
+ outputDir: 'apps/mobile',
19
+ options: [
20
+ { id: 'rn', label: 'React Native', templatePath: 'templates/mobile/rn', lang: 'js' },
21
+ { id: 'expo', label: 'Expo', templatePath: 'templates/mobile/expo', lang: 'js' },
22
+ { id: 'flutter', label: 'Flutter', templatePath: 'templates/mobile/flutter', lang: 'dart' },
23
+ ],
24
+ },
25
+ {
26
+ id: 'fe',
27
+ label: 'Frontend',
28
+ outputDir: 'apps/web',
29
+ options: [
30
+ { id: 'nextjs', label: 'Next.js', templatePath: 'templates/fe/nextjs', lang: 'js' },
31
+ { id: 'reactjs', label: 'ReactJS', templatePath: 'templates/fe/reactjs', lang: 'js' },
32
+ ],
33
+ },
34
+ {
35
+ id: 'be',
36
+ label: 'Backend',
37
+ outputDir: 'apps/backend',
38
+ options: [
39
+ { id: 'nodejs', label: 'Node.js', templatePath: 'templates/be/nodejs', lang: 'js' },
40
+ { id: 'nestjs', label: 'NestJS', templatePath: 'templates/be/nestjs', lang: 'js' },
41
+ { id: 'php', label: 'PHP', templatePath: 'templates/be/php', lang: 'php' },
42
+ ],
43
+ },
44
+ ];
45
+
46
+ // path tới folder template dùng chung (chỉ copy khi >= 2 lựa chọn lang === 'js')
47
+ export const SHARED_TEMPLATE_PATH = 'templates/shared';
48
+ export const SHARED_OUTPUT_DIR = 'packages/shared';
49
+
50
+ // Tra cứu nhanh 1 option theo (groupId, optionId).
51
+ export function findOption(groupId, optionId) {
52
+ const group = GROUPS.find((g) => g.id === groupId);
53
+ if (!group) return null;
54
+ const option = group.options.find((o) => o.id === optionId);
55
+ if (!option) return null;
56
+ return { group, option };
57
+ }
@@ -0,0 +1,11 @@
1
+ # Placeholder: NestJS (be/nestjs)
2
+
3
+ Đây là **placeholder** cho template **NestJS**.
4
+
5
+ Bỏ source chuẩn của NestJS vào đây. Lưu ý quy ước:
6
+
7
+ - Đặt `package.json` của template thành **`_package.json`** (CLI sẽ tự rename lại khi generate).
8
+ - Đặt `.gitignore` thành **`_gitignore`**, `.npmrc` thành **`_npmrc`** nếu có.
9
+ - Bất kỳ chỗ nào cần tên project, ghi `{{PROJECT_NAME}}` — CLI sẽ thay bằng tên thật.
10
+
11
+ `lang: js` → app này được tính vào điều kiện tạo `packages/shared`.
@@ -0,0 +1,6 @@
1
+ {
2
+ "name": "{{PROJECT_NAME}}-backend",
3
+ "version": "0.1.0",
4
+ "private": true,
5
+ "description": "Placeholder NestJS backend cho {{PROJECT_NAME}}"
6
+ }
@@ -0,0 +1,11 @@
1
+ # Placeholder: Node.js (be/nodejs)
2
+
3
+ Đây là **placeholder** cho template **Node.js**.
4
+
5
+ Bỏ source chuẩn của Node.js backend vào đây. Lưu ý quy ước:
6
+
7
+ - Đặt `package.json` của template thành **`_package.json`** (CLI sẽ tự rename lại khi generate).
8
+ - Đặt `.gitignore` thành **`_gitignore`**, `.npmrc` thành **`_npmrc`** nếu có.
9
+ - Bất kỳ chỗ nào cần tên project, ghi `{{PROJECT_NAME}}` — CLI sẽ thay bằng tên thật.
10
+
11
+ `lang: js` → app này được tính vào điều kiện tạo `packages/shared`.
@@ -0,0 +1,6 @@
1
+ {
2
+ "name": "{{PROJECT_NAME}}-backend",
3
+ "version": "0.1.0",
4
+ "private": true,
5
+ "description": "Placeholder Node.js backend cho {{PROJECT_NAME}}"
6
+ }
@@ -0,0 +1,15 @@
1
+ # Placeholder: PHP (be/php)
2
+
3
+ Đây là **placeholder** cho template **PHP**.
4
+
5
+ Bỏ source chuẩn của PHP backend vào đây.
6
+
7
+ > **Lưu ý riêng cho PHP (`lang: php`):**
8
+ > - **Không** có `_package.json` (PHP dùng `composer.json`, không phải npm).
9
+ > - **Không** nằm trong npm workspaces của project.
10
+ > - **Không** dùng `packages/shared` (shared chỉ dành cho hệ JS/TS).
11
+ >
12
+ > Khi generate, CLI vẫn copy nguyên folder này vào `apps/backend` và thay `{{PROJECT_NAME}}`
13
+ > trong các file text (vd `composer.json`, `README.md`).
14
+
15
+ Bạn có thể đặt `{{PROJECT_NAME}}` trong `composer.json` để CLI tự điền tên project.
@@ -0,0 +1,11 @@
1
+ # Placeholder: Next.js (fe/nextjs)
2
+
3
+ Đây là **placeholder** cho template **Next.js**.
4
+
5
+ Bỏ source chuẩn của Next.js vào đây. Lưu ý quy ước:
6
+
7
+ - Đặt `package.json` của template thành **`_package.json`** (CLI sẽ tự rename lại khi generate).
8
+ - Đặt `.gitignore` thành **`_gitignore`**, `.npmrc` thành **`_npmrc`** nếu có.
9
+ - Bất kỳ chỗ nào cần tên project, ghi `{{PROJECT_NAME}}` — CLI sẽ thay bằng tên thật.
10
+
11
+ `lang: js` → app này được tính vào điều kiện tạo `packages/shared`.
@@ -0,0 +1,6 @@
1
+ {
2
+ "name": "{{PROJECT_NAME}}-web",
3
+ "version": "0.1.0",
4
+ "private": true,
5
+ "description": "Placeholder Next.js app cho {{PROJECT_NAME}}"
6
+ }
@@ -0,0 +1,11 @@
1
+ # Placeholder: ReactJS (fe/reactjs)
2
+
3
+ Đây là **placeholder** cho template **ReactJS**.
4
+
5
+ Bỏ source chuẩn của ReactJS vào đây. Lưu ý quy ước:
6
+
7
+ - Đặt `package.json` của template thành **`_package.json`** (CLI sẽ tự rename lại khi generate).
8
+ - Đặt `.gitignore` thành **`_gitignore`**, `.npmrc` thành **`_npmrc`** nếu có.
9
+ - Bất kỳ chỗ nào cần tên project, ghi `{{PROJECT_NAME}}` — CLI sẽ thay bằng tên thật.
10
+
11
+ `lang: js` → app này được tính vào điều kiện tạo `packages/shared`.
@@ -0,0 +1,6 @@
1
+ {
2
+ "name": "{{PROJECT_NAME}}-web",
3
+ "version": "0.1.0",
4
+ "private": true,
5
+ "description": "Placeholder ReactJS app cho {{PROJECT_NAME}}"
6
+ }
@@ -0,0 +1,11 @@
1
+ # Placeholder: Expo (mobile/expo)
2
+
3
+ Đây là **placeholder** cho template **Expo**.
4
+
5
+ Bỏ source chuẩn của Expo vào đây. Lưu ý quy ước:
6
+
7
+ - Đặt `package.json` của template thành **`_package.json`** (CLI sẽ tự rename lại khi generate).
8
+ - Đặt `.gitignore` thành **`_gitignore`**, `.npmrc` thành **`_npmrc`** nếu có.
9
+ - Bất kỳ chỗ nào cần tên project, ghi `{{PROJECT_NAME}}` — CLI sẽ thay bằng tên thật.
10
+
11
+ `lang: js` → app này được tính vào điều kiện tạo `packages/shared`.
@@ -0,0 +1,6 @@
1
+ {
2
+ "name": "{{PROJECT_NAME}}-mobile",
3
+ "version": "0.1.0",
4
+ "private": true,
5
+ "description": "Placeholder Expo app cho {{PROJECT_NAME}}"
6
+ }
@@ -0,0 +1,15 @@
1
+ # Placeholder: Flutter (mobile/flutter)
2
+
3
+ Đây là **placeholder** cho template **Flutter**.
4
+
5
+ Bỏ source chuẩn của Flutter (Dart) vào đây.
6
+
7
+ > **Lưu ý riêng cho Flutter (`lang: dart`):**
8
+ > - **Không** có `_package.json` (Flutter dùng `pubspec.yaml`, không phải npm).
9
+ > - **Không** nằm trong npm workspaces của project.
10
+ > - **Không** dùng `packages/shared` (shared chỉ dành cho hệ JS/TS).
11
+ >
12
+ > Khi generate, CLI vẫn copy nguyên folder này vào `apps/mobile` và thay `{{PROJECT_NAME}}`
13
+ > trong các file text (vd `pubspec.yaml`, `README.md`).
14
+
15
+ Bạn có thể đặt `{{PROJECT_NAME}}` trong `pubspec.yaml` để CLI tự điền tên project.
@@ -0,0 +1,11 @@
1
+ # Placeholder: React Native (mobile/rn)
2
+
3
+ Đây là **placeholder** cho template **React Native**.
4
+
5
+ Bỏ source chuẩn của React Native vào đây. Lưu ý quy ước:
6
+
7
+ - Đặt `package.json` của template thành **`_package.json`** (CLI sẽ tự rename lại khi generate).
8
+ - Đặt `.gitignore` thành **`_gitignore`**, `.npmrc` thành **`_npmrc`** nếu có.
9
+ - Bất kỳ chỗ nào cần tên project, ghi `{{PROJECT_NAME}}` — CLI sẽ thay bằng tên thật.
10
+
11
+ `lang: js` → app này được tính vào điều kiện tạo `packages/shared`.
@@ -0,0 +1,6 @@
1
+ {
2
+ "name": "{{PROJECT_NAME}}-mobile",
3
+ "version": "0.1.0",
4
+ "private": true,
5
+ "description": "Placeholder React Native app cho {{PROJECT_NAME}}"
6
+ }
@@ -0,0 +1,11 @@
1
+ # Placeholder: shared (packages/shared)
2
+
3
+ Đây là **placeholder** cho package dùng chung của hệ **JS/TS**: types + api client.
4
+
5
+ Bỏ source chuẩn (types, api client...) dùng chung cho các app JS/TS vào đây. Lưu ý quy ước:
6
+
7
+ - Đặt `package.json` của template thành **`_package.json`** (CLI sẽ tự rename lại khi generate).
8
+ - Bất kỳ chỗ nào cần tên project, ghi `{{PROJECT_NAME}}` — CLI sẽ thay bằng tên thật.
9
+
10
+ > Package này **chỉ được tạo** khi project có **>= 2** lựa chọn `lang === 'js'`.
11
+ > Nếu ít hơn, CLI sẽ bỏ qua và in thông báo.
@@ -0,0 +1,8 @@
1
+ {
2
+ "name": "@{{PROJECT_NAME}}/shared",
3
+ "version": "0.1.0",
4
+ "private": true,
5
+ "description": "Types + api client dùng chung cho {{PROJECT_NAME}}",
6
+ "main": "index.js",
7
+ "type": "module"
8
+ }
@@ -0,0 +1,4 @@
1
+ // Package dùng chung cho {{PROJECT_NAME}}.
2
+ // Placeholder — bỏ types + api client thật vào đây.
3
+
4
+ export const PROJECT_NAME = '{{PROJECT_NAME}}';