lapeeh 1.0.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/.env.example +14 -0
- package/LICENSE +21 -0
- package/bin/index.js +934 -0
- package/doc/en/ARCHITECTURE_GUIDE.md +79 -0
- package/doc/en/CHANGELOG.md +203 -0
- package/doc/en/CHEATSHEET.md +90 -0
- package/doc/en/CLI.md +111 -0
- package/doc/en/CONTRIBUTING.md +119 -0
- package/doc/en/DEPLOYMENT.md +171 -0
- package/doc/en/FAQ.md +69 -0
- package/doc/en/FEATURES.md +99 -0
- package/doc/en/GETTING_STARTED.md +84 -0
- package/doc/en/INTRODUCTION.md +62 -0
- package/doc/en/PACKAGES.md +63 -0
- package/doc/en/PERFORMANCE.md +98 -0
- package/doc/en/ROADMAP.md +104 -0
- package/doc/en/SECURITY.md +95 -0
- package/doc/en/STRUCTURE.md +79 -0
- package/doc/en/TUTORIAL.md +145 -0
- package/doc/id/ARCHITECTURE_GUIDE.md +76 -0
- package/doc/id/CHANGELOG.md +203 -0
- package/doc/id/CHEATSHEET.md +90 -0
- package/doc/id/CLI.md +139 -0
- package/doc/id/CONTRIBUTING.md +119 -0
- package/doc/id/DEPLOYMENT.md +171 -0
- package/doc/id/FAQ.md +69 -0
- package/doc/id/FEATURES.md +169 -0
- package/doc/id/GETTING_STARTED.md +91 -0
- package/doc/id/INTRODUCTION.md +62 -0
- package/doc/id/PACKAGES.md +63 -0
- package/doc/id/PERFORMANCE.md +100 -0
- package/doc/id/ROADMAP.md +107 -0
- package/doc/id/SECURITY.md +94 -0
- package/doc/id/STRUCTURE.md +79 -0
- package/doc/id/TUTORIAL.md +145 -0
- package/docker-compose.yml +24 -0
- package/ecosystem.config.js +17 -0
- package/eslint.config.mjs +26 -0
- package/gitignore.template +30 -0
- package/lib/bootstrap.ts +210 -0
- package/lib/core/realtime.ts +34 -0
- package/lib/core/redis.ts +139 -0
- package/lib/core/serializer.ts +63 -0
- package/lib/core/server.ts +70 -0
- package/lib/core/store.ts +116 -0
- package/lib/middleware/auth.ts +63 -0
- package/lib/middleware/error.ts +50 -0
- package/lib/middleware/multipart.ts +13 -0
- package/lib/middleware/rateLimit.ts +14 -0
- package/lib/middleware/requestLogger.ts +27 -0
- package/lib/middleware/visitor.ts +178 -0
- package/lib/utils/logger.ts +100 -0
- package/lib/utils/pagination.ts +56 -0
- package/lib/utils/response.ts +88 -0
- package/lib/utils/validator.ts +394 -0
- package/nodemon.json +6 -0
- package/package.json +126 -0
- package/readme.md +357 -0
- package/scripts/check-update.js +92 -0
- package/scripts/config-clear.js +45 -0
- package/scripts/generate-jwt-secret.js +38 -0
- package/scripts/init-project.js +84 -0
- package/scripts/make-module.js +89 -0
- package/scripts/release.js +494 -0
- package/scripts/seed-json.js +158 -0
- package/scripts/verify-rbac-functional.js +187 -0
- package/src/config/app.ts +9 -0
- package/src/config/cors.ts +5 -0
- package/src/modules/Auth/auth.controller.ts +519 -0
- package/src/modules/Rbac/rbac.controller.ts +533 -0
- package/src/routes/auth.ts +74 -0
- package/src/routes/index.ts +7 -0
- package/src/routes/rbac.ts +42 -0
- package/storage/logs/.gitkeep +0 -0
- package/tsconfig.build.json +12 -0
- package/tsconfig.json +30 -0
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
|
|
4
|
+
const args = process.argv.slice(2);
|
|
5
|
+
const moduleName = args[0];
|
|
6
|
+
|
|
7
|
+
if (!moduleName) {
|
|
8
|
+
console.error('ā Please specify the module name.');
|
|
9
|
+
console.error(' Usage: npm run make:module <ModuleName>');
|
|
10
|
+
process.exit(1);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
// Capitalize first letter
|
|
14
|
+
const name = moduleName.charAt(0).toUpperCase() + moduleName.slice(1);
|
|
15
|
+
const lowerName = moduleName.toLowerCase();
|
|
16
|
+
|
|
17
|
+
const moduleDir = path.join(__dirname, '..', 'src', 'modules', name);
|
|
18
|
+
|
|
19
|
+
if (fs.existsSync(moduleDir)) {
|
|
20
|
+
console.error(`ā Module ${name} already exists at ${moduleDir}`);
|
|
21
|
+
process.exit(1);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
fs.mkdirSync(moduleDir, { recursive: true });
|
|
25
|
+
|
|
26
|
+
// Controller
|
|
27
|
+
const controllerContent = `import { Request, Response } from "express";
|
|
28
|
+
import { sendSuccess } from "@lapeeh/utils/response";
|
|
29
|
+
// import * as ${name}Service from "./${lowerName}.service";
|
|
30
|
+
|
|
31
|
+
export async function index(_req: Request, res: Response) {
|
|
32
|
+
sendSuccess(res, 200, "Index ${name}");
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export async function show(req: Request, res: Response) {
|
|
36
|
+
const { id } = req.params;
|
|
37
|
+
sendSuccess(res, 200, "Show ${name} " + id);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export async function create(_req: Request, res: Response) {
|
|
41
|
+
sendSuccess(res, 201, "Create ${name}");
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export async function update(req: Request, res: Response) {
|
|
45
|
+
const { id } = req.params;
|
|
46
|
+
sendSuccess(res, 200, "Update ${name} " + id);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export async function destroy(req: Request, res: Response) {
|
|
50
|
+
const { id } = req.params;
|
|
51
|
+
sendSuccess(res, 200, "Delete ${name} " + id);
|
|
52
|
+
}
|
|
53
|
+
`;
|
|
54
|
+
|
|
55
|
+
fs.writeFileSync(path.join(moduleDir, `${lowerName}.controller.ts`), controllerContent);
|
|
56
|
+
|
|
57
|
+
// Service (Optional but good for NestJS style)
|
|
58
|
+
const serviceContent = `
|
|
59
|
+
export async function findAll() {
|
|
60
|
+
return [];
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export async function findOne(_id: number) {
|
|
64
|
+
return null;
|
|
65
|
+
}
|
|
66
|
+
`;
|
|
67
|
+
fs.writeFileSync(path.join(moduleDir, `${lowerName}.service.ts`), serviceContent);
|
|
68
|
+
|
|
69
|
+
// Route Stub
|
|
70
|
+
const routeContent = `import { Router } from "express";
|
|
71
|
+
import * as ${name}Controller from "./${lowerName}.controller";
|
|
72
|
+
|
|
73
|
+
const router = Router();
|
|
74
|
+
|
|
75
|
+
router.get("/", ${name}Controller.index);
|
|
76
|
+
router.get("/:id", ${name}Controller.show);
|
|
77
|
+
router.post("/", ${name}Controller.create);
|
|
78
|
+
router.put("/:id", ${name}Controller.update);
|
|
79
|
+
router.delete("/:id", ${name}Controller.destroy);
|
|
80
|
+
|
|
81
|
+
export default router;
|
|
82
|
+
`;
|
|
83
|
+
fs.writeFileSync(path.join(moduleDir, `${lowerName}.routes.ts`), routeContent);
|
|
84
|
+
|
|
85
|
+
console.log(`ā
Module ${name} created successfully at src/modules/${name}`);
|
|
86
|
+
console.log(` - ${lowerName}.controller.ts`);
|
|
87
|
+
console.log(` - ${lowerName}.service.ts`);
|
|
88
|
+
console.log(` - ${lowerName}.routes.ts`);
|
|
89
|
+
console.log(`\nš Don't forget to register the route in src/routes/index.ts!`);
|
|
@@ -0,0 +1,494 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const readline = require('readline');
|
|
4
|
+
const { execSync } = require('child_process');
|
|
5
|
+
|
|
6
|
+
const rl = readline.createInterface({
|
|
7
|
+
input: process.stdin,
|
|
8
|
+
output: process.stdout
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
const question = (query) => new Promise((resolve) => rl.question(query, resolve));
|
|
12
|
+
|
|
13
|
+
// Paths
|
|
14
|
+
const rootDir = path.resolve(__dirname, '..');
|
|
15
|
+
const websiteDir = path.join(rootDir, 'website');
|
|
16
|
+
const packageJsonPath = path.join(rootDir, 'package.json');
|
|
17
|
+
const websitePackageJsonPath = path.join(websiteDir, 'package.json');
|
|
18
|
+
|
|
19
|
+
// Read current version
|
|
20
|
+
const pkg = require(packageJsonPath);
|
|
21
|
+
const currentVersion = pkg.version;
|
|
22
|
+
|
|
23
|
+
console.log(`\nš lapeeh Release Automation Script`);
|
|
24
|
+
console.log(`Current Version: ${currentVersion}\n`);
|
|
25
|
+
|
|
26
|
+
// Helper to get git changes
|
|
27
|
+
function getGitChanges() {
|
|
28
|
+
try {
|
|
29
|
+
// Try to find the last tag
|
|
30
|
+
let lastTag = '';
|
|
31
|
+
try {
|
|
32
|
+
lastTag = execSync('git describe --tags --abbrev=0', { stdio: 'pipe' }).toString().trim();
|
|
33
|
+
} catch (e) {
|
|
34
|
+
// No tags found, maybe fetch all commits
|
|
35
|
+
lastTag = '';
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const range = lastTag ? `${lastTag}..HEAD` : 'HEAD';
|
|
39
|
+
const logs = execSync(`git log ${range} --pretty=format:"%s"`, { stdio: 'pipe' }).toString().trim();
|
|
40
|
+
|
|
41
|
+
if (!logs) return [];
|
|
42
|
+
|
|
43
|
+
// Filter out chores, merges, etc. if desired, or keep everything
|
|
44
|
+
return logs.split('\n')
|
|
45
|
+
.map(l => l.trim())
|
|
46
|
+
.filter(l =>
|
|
47
|
+
l &&
|
|
48
|
+
!l.startsWith('chore:') &&
|
|
49
|
+
!l.startsWith('Merge branch') &&
|
|
50
|
+
!l.startsWith('docs: release') &&
|
|
51
|
+
!l.includes('release v') &&
|
|
52
|
+
!l.includes('Update version')
|
|
53
|
+
);
|
|
54
|
+
} catch (e) {
|
|
55
|
+
return [];
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Helper to get npm version
|
|
60
|
+
function getNpmVersion() {
|
|
61
|
+
try {
|
|
62
|
+
return execSync('npm view lapeeh version', { stdio: 'pipe' }).toString().trim();
|
|
63
|
+
} catch (e) {
|
|
64
|
+
return null;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// Helper to increment patch version
|
|
69
|
+
function incrementPatch(version) {
|
|
70
|
+
const parts = version.split('.').map(Number);
|
|
71
|
+
if (parts.length !== 3 || parts.some(isNaN)) return version; // Fallback
|
|
72
|
+
parts[2]++;
|
|
73
|
+
return parts.join('.');
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Helper to generate auto commit message
|
|
77
|
+
function generateAutoCommitMessage() {
|
|
78
|
+
try {
|
|
79
|
+
const status = execSync('git status --porcelain', { stdio: 'pipe' }).toString().trim();
|
|
80
|
+
if (!status) return 'chore: no changes detected';
|
|
81
|
+
|
|
82
|
+
const files = status.split('\n').map(line => line.substring(3).trim());
|
|
83
|
+
|
|
84
|
+
const hasDocs = files.some(f => f.startsWith('website/') || f.startsWith('doc/') || f.endsWith('.md'));
|
|
85
|
+
const hasScripts = files.some(f => f.startsWith('scripts/'));
|
|
86
|
+
const hasPackage = files.some(f => f.includes('package.json'));
|
|
87
|
+
const hasSrc = files.some(f => !f.startsWith('website/') && !f.startsWith('doc/') && !f.startsWith('scripts/') && !f.includes('package.json') && !f.startsWith('.'));
|
|
88
|
+
|
|
89
|
+
let types = [];
|
|
90
|
+
if (hasDocs) types.push('docs');
|
|
91
|
+
if (hasScripts) types.push('scripts');
|
|
92
|
+
if (hasPackage) types.push('deps');
|
|
93
|
+
if (hasSrc) types.push('feat/fix');
|
|
94
|
+
|
|
95
|
+
if (types.length === 0) return 'chore: update project files';
|
|
96
|
+
|
|
97
|
+
if (hasDocs && !hasSrc && !hasScripts) return 'docs: update documentation';
|
|
98
|
+
if (hasScripts && !hasSrc) return 'chore: update build scripts';
|
|
99
|
+
|
|
100
|
+
return `chore: update ${types.join(', ')}`;
|
|
101
|
+
} catch (e) {
|
|
102
|
+
return 'chore: update project files';
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// Helper to parse changelog entry with structure
|
|
107
|
+
function parseChangelogEntry(filePath, version) {
|
|
108
|
+
try {
|
|
109
|
+
if (!fs.existsSync(filePath)) return null;
|
|
110
|
+
const content = fs.readFileSync(filePath, 'utf8');
|
|
111
|
+
|
|
112
|
+
// 1. Find the header line to extract Title
|
|
113
|
+
// Regex matches: ## [Date] - Day, Date - Title (vVersion)
|
|
114
|
+
const headerRegex = new RegExp(`## \\[.*?\\] - .*? - (.*?) \\(v${version}\\)`, 'i');
|
|
115
|
+
const headerMatch = content.match(headerRegex);
|
|
116
|
+
const title = headerMatch ? headerMatch[1].trim() : null;
|
|
117
|
+
|
|
118
|
+
// 2. Extract the body
|
|
119
|
+
const bodyRegex = new RegExp(`## \\[.*?\\] - .*?v${version}.*?([\\s\\S]*?)(?=\\n## \\[|$)`, 'i');
|
|
120
|
+
const bodyMatch = content.match(bodyRegex);
|
|
121
|
+
|
|
122
|
+
if (!bodyMatch) return null;
|
|
123
|
+
|
|
124
|
+
let rawBody = bodyMatch[1].trim();
|
|
125
|
+
|
|
126
|
+
// 3. Extract Intro (text before first ###)
|
|
127
|
+
let intro = '';
|
|
128
|
+
let features = rawBody;
|
|
129
|
+
|
|
130
|
+
const firstHeaderIndex = rawBody.indexOf('###');
|
|
131
|
+
if (firstHeaderIndex > 0) {
|
|
132
|
+
intro = rawBody.substring(0, firstHeaderIndex).trim();
|
|
133
|
+
features = rawBody.substring(firstHeaderIndex).trim();
|
|
134
|
+
} else if (firstHeaderIndex === -1 && !rawBody.startsWith('-') && !rawBody.startsWith('*')) {
|
|
135
|
+
// If no subheaders and doesn't start with list, treat as intro
|
|
136
|
+
intro = rawBody;
|
|
137
|
+
features = '';
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
return {
|
|
141
|
+
title,
|
|
142
|
+
intro,
|
|
143
|
+
features
|
|
144
|
+
};
|
|
145
|
+
} catch (e) {
|
|
146
|
+
return null;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
async function main() {
|
|
151
|
+
try {
|
|
152
|
+
// 0. Quick Git Update Check
|
|
153
|
+
const quickGit = await question('\nā” Apakah ini hanya Quick Git Update (tanpa rilis versi)? (y/n): ');
|
|
154
|
+
if (quickGit.toLowerCase() === 'y') {
|
|
155
|
+
console.log('\nš¤ Auto-generating commit message...');
|
|
156
|
+
const commitMsg = generateAutoCommitMessage();
|
|
157
|
+
console.log(`š Commit Message: "${commitMsg}"`);
|
|
158
|
+
|
|
159
|
+
try {
|
|
160
|
+
console.log('š Syncing documentation (just in case)...');
|
|
161
|
+
try {
|
|
162
|
+
execSync('node scripts/sync-docs.js', { cwd: websiteDir, stdio: 'inherit' });
|
|
163
|
+
} catch (e) {
|
|
164
|
+
console.log('ā ļø Warning: Doc sync failed, continuing...');
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
execSync('git add .', { stdio: 'inherit' });
|
|
168
|
+
execSync(`git commit -m "${commitMsg}"`, { stdio: 'inherit' });
|
|
169
|
+
execSync('git push origin HEAD', { stdio: 'inherit' });
|
|
170
|
+
console.log('ā
Quick Git Update selesai!');
|
|
171
|
+
process.exit(0);
|
|
172
|
+
} catch (e) {
|
|
173
|
+
console.error('ā Error during git operations:', e.message);
|
|
174
|
+
process.exit(1);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// 1. Check versions and ask for new one
|
|
179
|
+
console.log('š Checking npm version...');
|
|
180
|
+
const npmVersion = getNpmVersion();
|
|
181
|
+
const baseVersion = npmVersion || currentVersion;
|
|
182
|
+
const suggestedVersion = incrementPatch(baseVersion);
|
|
183
|
+
|
|
184
|
+
console.log(`Latest npm version: ${npmVersion || 'Not found (using local)'}`);
|
|
185
|
+
console.log(`Current local version: ${currentVersion}`);
|
|
186
|
+
|
|
187
|
+
const newVersionInput = await question(`Enter new version (default: ${suggestedVersion}): `);
|
|
188
|
+
const newVersion = newVersionInput.trim() || suggestedVersion;
|
|
189
|
+
|
|
190
|
+
if (!newVersion) {
|
|
191
|
+
console.log('ā Version is required');
|
|
192
|
+
process.exit(1);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// Always update package.json locally first
|
|
196
|
+
console.log('\nš¦ Updating package.json files...');
|
|
197
|
+
updatePackageJson(packageJsonPath, newVersion);
|
|
198
|
+
updatePackageJson(websitePackageJsonPath, newVersion);
|
|
199
|
+
|
|
200
|
+
// 2. Question: Blog
|
|
201
|
+
const createBlog = await question('\n1. Apa Anda akan membuatkan blog untuk fitur baru ini? (y/n): ');
|
|
202
|
+
let blogTitleEN = '';
|
|
203
|
+
|
|
204
|
+
if (createBlog.toLowerCase() === 'y') {
|
|
205
|
+
|
|
206
|
+
const useAuto = await question('š¤ Auto-generate content from CHANGELOG/Git? (y/n): ');
|
|
207
|
+
|
|
208
|
+
let titleID, descriptionID, introID, featureListID;
|
|
209
|
+
let titleEN, descriptionEN, introEN, featureListEN;
|
|
210
|
+
|
|
211
|
+
if (useAuto.toLowerCase() === 'y') {
|
|
212
|
+
console.log('\nš¤ Auto-detecting changes from Git & Changelog...');
|
|
213
|
+
const changes = getGitChanges();
|
|
214
|
+
|
|
215
|
+
// Try to read from CHANGELOG.md first
|
|
216
|
+
const parsedID = parseChangelogEntry(path.join(rootDir, 'doc/id/CHANGELOG.md'), newVersion);
|
|
217
|
+
const parsedEN = parseChangelogEntry(path.join(rootDir, 'doc/en/CHANGELOG.md'), newVersion);
|
|
218
|
+
|
|
219
|
+
if (parsedID) {
|
|
220
|
+
console.log('ā
Found entry in doc/id/CHANGELOG.md');
|
|
221
|
+
titleID = parsedID.title || `Update Terbaru v${newVersion}`;
|
|
222
|
+
introID = parsedID.intro || `Kami dengan bangga mengumumkan rilis **lapeeh v${newVersion}**. Update ini menghadirkan **${parsedID.title || 'berbagai fitur baru'}** untuk meningkatkan pengalaman pengembangan Anda.`;
|
|
223
|
+
descriptionID = parsedID.intro ? parsedID.intro.split('\n')[0] : `Rilis versi ${newVersion} hadir dengan berbagai pembaruan dan perbaikan.`;
|
|
224
|
+
featureListID = parsedID.features;
|
|
225
|
+
} else {
|
|
226
|
+
console.log('ā ļø No entry in doc/id/CHANGELOG.md, using git logs...');
|
|
227
|
+
titleID = changes.length > 0 ? changes[0] : 'Maintenance Release';
|
|
228
|
+
descriptionID = changes.length > 0 ? `Includes: ${changes.slice(0, 2).join(', ')}` : 'Routine maintenance and updates.';
|
|
229
|
+
introID = `Kami dengan bangga mengumumkan rilis **lapeeh v${newVersion}**. Rilis ini mencakup pemeliharaan rutin dan perbaikan bug.`;
|
|
230
|
+
featureListID = changes.length > 0
|
|
231
|
+
? changes.map(f => `* **${f.trim()}**`).join('\n')
|
|
232
|
+
: '* **Routine maintenance**';
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
if (parsedEN) {
|
|
236
|
+
console.log('ā
Found entry in doc/en/CHANGELOG.md');
|
|
237
|
+
titleEN = parsedEN.title || `Latest Update v${newVersion}`;
|
|
238
|
+
introEN = parsedEN.intro || `We are proud to announce the release of **lapeeh v${newVersion}**. This update brings **${parsedEN.title || 'various new features'}** to enhance your development experience.`;
|
|
239
|
+
descriptionEN = parsedEN.intro ? parsedEN.intro.split('\n')[0] : `Release version ${newVersion} comes with various updates and improvements.`;
|
|
240
|
+
featureListEN = parsedEN.features;
|
|
241
|
+
} else {
|
|
242
|
+
console.log('ā ļø No entry in doc/en/CHANGELOG.md, using git logs...');
|
|
243
|
+
titleEN = changes.length > 0 ? changes[0] : 'Maintenance Release';
|
|
244
|
+
descriptionEN = changes.length > 0 ? `Includes: ${changes.slice(0, 2).join(', ')}` : 'Routine maintenance and updates.';
|
|
245
|
+
introEN = `We are proud to announce the release of **lapeeh v${newVersion}**. This release includes routine maintenance and bug fixes.`;
|
|
246
|
+
featureListEN = changes.length > 0
|
|
247
|
+
? changes.map(f => `* **${f.trim()}**`).join('\n')
|
|
248
|
+
: '* **Routine maintenance**';
|
|
249
|
+
}
|
|
250
|
+
} else {
|
|
251
|
+
console.log('\nš Manual Blog Entry');
|
|
252
|
+
console.log('Silakan masukkan detail blog secara manual.');
|
|
253
|
+
|
|
254
|
+
// ID Inputs
|
|
255
|
+
titleID = await question('Judul Blog (ID): ');
|
|
256
|
+
descriptionID = await question('Deskripsi Singkat (ID): ');
|
|
257
|
+
const contentID = await question('Konten Utama/Fitur (ID) - Gunakan format Markdown jika perlu: ');
|
|
258
|
+
introID = `Rilis versi ${newVersion} telah hadir.`; // Fallback for manual
|
|
259
|
+
featureListID = contentID;
|
|
260
|
+
|
|
261
|
+
console.log('\n--- English Version ---');
|
|
262
|
+
|
|
263
|
+
// EN Inputs
|
|
264
|
+
titleEN = await question('Blog Title (EN): ');
|
|
265
|
+
descriptionEN = await question('Short Description (EN): ');
|
|
266
|
+
const contentEN = await question('Main Content/Features (EN): ');
|
|
267
|
+
introEN = `Release version ${newVersion} is here.`; // Fallback for manual
|
|
268
|
+
featureListEN = contentEN;
|
|
269
|
+
|
|
270
|
+
// Set defaults if empty
|
|
271
|
+
if (!titleID) titleID = `Update v${newVersion}`;
|
|
272
|
+
if (!descriptionID) descriptionID = `Pembaruan versi ${newVersion}`;
|
|
273
|
+
if (!featureListID) featureListID = '* Pembaruan rutin';
|
|
274
|
+
|
|
275
|
+
if (!titleEN) titleEN = `Update v${newVersion}`;
|
|
276
|
+
if (!descriptionEN) descriptionEN = `Update version ${newVersion}`;
|
|
277
|
+
if (!featureListEN) featureListEN = '* Routine updates';
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
blogTitleEN = titleEN; // Save for commit message
|
|
281
|
+
|
|
282
|
+
console.log('š Generating blog posts...');
|
|
283
|
+
const date = new Date().toISOString().split('T')[0]; // YYYY-MM-DD
|
|
284
|
+
const dateString = new Date().toLocaleDateString('id-ID', { day: 'numeric', month: 'long', year: 'numeric' });
|
|
285
|
+
const dateStringEn = new Date().toLocaleDateString('en-US', { month: 'long', day: 'numeric', year: 'numeric' });
|
|
286
|
+
|
|
287
|
+
const blogFileName = `release-v${newVersion}.md`;
|
|
288
|
+
|
|
289
|
+
// Indonesian Blog Content
|
|
290
|
+
const idContent = `---
|
|
291
|
+
title: "Rilis lapeeh v${newVersion}: ${titleID}"
|
|
292
|
+
date: ${date}
|
|
293
|
+
author: Tim lapeeh
|
|
294
|
+
description: "${descriptionID.replace(/"/g, '\\"')}"
|
|
295
|
+
---
|
|
296
|
+
|
|
297
|
+
# Rilis lapeeh v${newVersion}: ${titleID}
|
|
298
|
+
|
|
299
|
+
Ditulis pada **${dateString}** oleh **Tim lapeeh**
|
|
300
|
+
|
|
301
|
+
${introID}
|
|
302
|
+
|
|
303
|
+
## Apa yang Baru? š
|
|
304
|
+
|
|
305
|
+
${featureListID}
|
|
306
|
+
|
|
307
|
+
## Cara Upgrade
|
|
308
|
+
|
|
309
|
+
Bagi pengguna baru, cukup jalankan:
|
|
310
|
+
|
|
311
|
+
\`\`\`bash
|
|
312
|
+
npx lapeeh init my-project
|
|
313
|
+
\`\`\`
|
|
314
|
+
|
|
315
|
+
Bagi pengguna lama yang ingin update ke versi terbaru:
|
|
316
|
+
|
|
317
|
+
\`\`\`bash
|
|
318
|
+
npm install lapeeh@latest
|
|
319
|
+
\`\`\`
|
|
320
|
+
|
|
321
|
+
Terima kasih telah menjadi bagian dari perjalanan lapeeh Framework!
|
|
322
|
+
`;
|
|
323
|
+
|
|
324
|
+
// English Blog Content
|
|
325
|
+
const enContent = `---
|
|
326
|
+
title: "Release lapeeh v${newVersion}: ${titleEN}"
|
|
327
|
+
date: ${date}
|
|
328
|
+
author: lapeeh Team
|
|
329
|
+
description: "${descriptionEN.replace(/"/g, '\\"')}"
|
|
330
|
+
---
|
|
331
|
+
|
|
332
|
+
# Release lapeeh v${newVersion}: ${titleEN}
|
|
333
|
+
|
|
334
|
+
Written on **${dateStringEn}** by **lapeeh Team**
|
|
335
|
+
|
|
336
|
+
${introEN}
|
|
337
|
+
|
|
338
|
+
## What's New? š
|
|
339
|
+
|
|
340
|
+
${featureListEN}
|
|
341
|
+
|
|
342
|
+
## How to Upgrade
|
|
343
|
+
|
|
344
|
+
For new users, simply run:
|
|
345
|
+
|
|
346
|
+
\`\`\`bash
|
|
347
|
+
npx lapeeh init my-project
|
|
348
|
+
\`\`\`
|
|
349
|
+
|
|
350
|
+
For existing users who want to update to the latest version:
|
|
351
|
+
|
|
352
|
+
\`\`\`bash
|
|
353
|
+
npm install lapeeh@latest
|
|
354
|
+
\`\`\`
|
|
355
|
+
|
|
356
|
+
Thank you for being part of the lapeeh Framework journey!
|
|
357
|
+
`;
|
|
358
|
+
|
|
359
|
+
fs.writeFileSync(path.join(websiteDir, 'blog', blogFileName), idContent);
|
|
360
|
+
fs.writeFileSync(path.join(websiteDir, 'en/blog', blogFileName), enContent);
|
|
361
|
+
|
|
362
|
+
console.log('š Updating blog indexes...');
|
|
363
|
+
updateBlogIndex(path.join(websiteDir, 'blog/index.md'), newVersion, titleID, dateString, descriptionID, blogFileName, 'id');
|
|
364
|
+
updateBlogIndex(path.join(websiteDir, 'en/blog/index.md'), newVersion, titleEN, dateStringEn, descriptionEN, blogFileName, 'en');
|
|
365
|
+
} else {
|
|
366
|
+
console.log('āļø Skipping blog generation.');
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
// 3. Question: Documentation
|
|
370
|
+
const updateDocs = await question('\n2. Apa Anda ingin update dokumentasi? (y/n): ');
|
|
371
|
+
if (updateDocs.toLowerCase() === 'y') {
|
|
372
|
+
console.log('\nš Documentation Update:');
|
|
373
|
+
console.log('Sistem akan menjalankan sinkronisasi otomatis:');
|
|
374
|
+
console.log(' - Menyalin file dari `doc/id` ke `website/docs`');
|
|
375
|
+
console.log(' - Menyalin file dari `doc/en` ke `website/en/docs`');
|
|
376
|
+
console.log(' - Mengubah nama file menjadi format URL-friendly (contoh: GETTING_STARTED.md -> getting-started.md)');
|
|
377
|
+
|
|
378
|
+
console.log('\nā ļø Manual Action Required (If applicable):');
|
|
379
|
+
console.log('Jika ada package/method baru, silakan update file berikut secara manual sekarang:');
|
|
380
|
+
console.log(' - website/docs/packages.md');
|
|
381
|
+
console.log(' - website/docs/api.md');
|
|
382
|
+
|
|
383
|
+
await question('Tekan Enter untuk menjalankan sinkronisasi otomatis (setelah Anda selesai update manual)...');
|
|
384
|
+
|
|
385
|
+
console.log('š Syncing documentation...');
|
|
386
|
+
execSync('node scripts/sync-docs.js', { cwd: websiteDir, stdio: 'inherit' });
|
|
387
|
+
} else {
|
|
388
|
+
console.log('āļø Skipping documentation sync.');
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
// 4. Question: Git
|
|
392
|
+
const pushGit = await question('\n3. Apa ingin publish ke Git? (y/n): ');
|
|
393
|
+
if (pushGit.toLowerCase() === 'y') {
|
|
394
|
+
const commitMsg = blogTitleEN
|
|
395
|
+
? `chore: release v${newVersion} - ${blogTitleEN}`
|
|
396
|
+
: `chore: release v${newVersion}`;
|
|
397
|
+
|
|
398
|
+
try {
|
|
399
|
+
execSync('git add .', { stdio: 'inherit' });
|
|
400
|
+
execSync(`git commit -m "${commitMsg}"`, { stdio: 'inherit' });
|
|
401
|
+
} catch (e) {
|
|
402
|
+
console.log('ā ļø No changes to commit or commit failed. Continuing...');
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
try {
|
|
406
|
+
// Delete tag if it exists locally to avoid "already exists" error
|
|
407
|
+
execSync(`git tag -d v${newVersion}`, { stdio: 'ignore' });
|
|
408
|
+
} catch (e) {
|
|
409
|
+
// Ignore if tag doesn't exist
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
execSync(`git tag v${newVersion}`, { stdio: 'inherit' });
|
|
413
|
+
execSync(`git push origin HEAD`, { stdio: 'inherit' });
|
|
414
|
+
|
|
415
|
+
try {
|
|
416
|
+
execSync(`git push origin v${newVersion}`, { stdio: 'inherit' });
|
|
417
|
+
} catch (e) {
|
|
418
|
+
console.log('ā ļø Tag push failed. Trying force push (updating existing tag)...');
|
|
419
|
+
execSync(`git push origin v${newVersion} --force`, { stdio: 'inherit' });
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
console.log('ā
Git push & tag complete');
|
|
423
|
+
} else {
|
|
424
|
+
console.log('āļø Skipping Git push.');
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
// 5. Question: NPM
|
|
428
|
+
const publishNpm = await question('\n4. Apa ingin publish ke NPM? (y/n): ');
|
|
429
|
+
if (publishNpm.toLowerCase() === 'y') {
|
|
430
|
+
try {
|
|
431
|
+
execSync('npm publish', { stdio: 'inherit' });
|
|
432
|
+
console.log('ā
NPM publish complete');
|
|
433
|
+
} catch (error) {
|
|
434
|
+
console.log('\nā ļø NPM Publish failed. This might be due to 2FA.');
|
|
435
|
+
const otp = await question('š Masukkan kode OTP (Authenticator App) Anda: ');
|
|
436
|
+
if (otp && otp.trim() !== '') {
|
|
437
|
+
execSync(`npm publish --otp=${otp.trim()}`, { stdio: 'inherit' });
|
|
438
|
+
console.log('ā
NPM publish complete');
|
|
439
|
+
} else {
|
|
440
|
+
console.log('ā NPM publish aborted.');
|
|
441
|
+
throw error;
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
} else {
|
|
445
|
+
console.log('āļø Skipping NPM publish.');
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
console.log('\n⨠Proses selesai!');
|
|
449
|
+
|
|
450
|
+
} catch (error) {
|
|
451
|
+
console.error('ā Error:', error.message);
|
|
452
|
+
} finally {
|
|
453
|
+
rl.close();
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
function updatePackageJson(filePath, version) {
|
|
458
|
+
const json = JSON.parse(fs.readFileSync(filePath, 'utf8'));
|
|
459
|
+
json.version = version;
|
|
460
|
+
fs.writeFileSync(filePath, JSON.stringify(json, null, 2) + '\n');
|
|
461
|
+
console.log(`Updated ${path.basename(filePath)} to ${version}`);
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
function updateBlogIndex(filePath, version, title, date, description, fileName, lang) {
|
|
465
|
+
let content = fs.readFileSync(filePath, 'utf8');
|
|
466
|
+
|
|
467
|
+
const readMore = lang === 'id' ? 'Baca selengkapnya' : 'Read more';
|
|
468
|
+
const releaseTag = lang === 'id' ? 'Rilis' : 'Release';
|
|
469
|
+
|
|
470
|
+
// Construct new entry
|
|
471
|
+
const newEntry = `## š [${releaseTag} v${version}: ${title}](./${fileName.replace('.md', '')})
|
|
472
|
+
|
|
473
|
+
_${date}_ ⢠š¤ lapeeh Team ⢠š·ļø _Release_
|
|
474
|
+
|
|
475
|
+
${description} [${readMore} ā](./${fileName.replace('.md', '')})
|
|
476
|
+
|
|
477
|
+
---
|
|
478
|
+
|
|
479
|
+
`;
|
|
480
|
+
|
|
481
|
+
const separator = '---';
|
|
482
|
+
const parts = content.split(separator);
|
|
483
|
+
|
|
484
|
+
if (parts.length >= 2) {
|
|
485
|
+
parts.splice(1, 0, '\n\n' + newEntry.trim() + '\n\n');
|
|
486
|
+
content = parts.join(separator);
|
|
487
|
+
} else {
|
|
488
|
+
content = content + '\n\n' + newEntry;
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
fs.writeFileSync(filePath, content);
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
main();
|