@skalfa/skalfa-cli 1.0.11 → 1.0.12
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/dist/bin/skalfa.js +15 -4
- package/dist/commands/add-extension.js +69 -0
- package/dist/commands/pick.js +131 -3
- package/package.json +1 -1
package/dist/bin/skalfa.js
CHANGED
|
@@ -89,10 +89,21 @@ program
|
|
|
89
89
|
});
|
|
90
90
|
program
|
|
91
91
|
.command("pick")
|
|
92
|
-
.description("Eject/copy a core utility
|
|
93
|
-
.argument("<
|
|
94
|
-
.
|
|
95
|
-
|
|
92
|
+
.description("Eject/copy a core utility or component into your local project for customization.")
|
|
93
|
+
.argument("<name>", "utility or component name")
|
|
94
|
+
.argument("[newName]", "new name for the component (optional, component only)")
|
|
95
|
+
.action(async (name, newName) => {
|
|
96
|
+
await runCommand(() => {
|
|
97
|
+
if (pick_1.UTILITIES.includes(name)) {
|
|
98
|
+
if (newName) {
|
|
99
|
+
throw new Error("Renaming is only supported for components, not utilities.");
|
|
100
|
+
}
|
|
101
|
+
(0, pick_1.pickUtility)(name);
|
|
102
|
+
}
|
|
103
|
+
else {
|
|
104
|
+
(0, pick_1.pickComponent)(name, newName);
|
|
105
|
+
}
|
|
106
|
+
});
|
|
96
107
|
});
|
|
97
108
|
program
|
|
98
109
|
.command("update")
|
|
@@ -43,6 +43,75 @@ async function addExtension(extensionName) {
|
|
|
43
43
|
(0, installer_1.installPackage)(projectRoot, isDev ? "file:../skalfa-idb" : "@skalfa/skalfa-idb");
|
|
44
44
|
addTsconfigPath(node_path_1.default.join(projectRoot, "tsconfig.json"), "@skalfa/skalfa-idb");
|
|
45
45
|
addUtilExport(node_path_1.default.join(projectRoot, "utils", "index.ts"), "@skalfa/skalfa-idb");
|
|
46
|
+
// Scaffold IDBProvider
|
|
47
|
+
console.log("Scaffolding IDBProvider...");
|
|
48
|
+
const providerDir = node_path_1.default.join(projectRoot, "components", "base.components", "wrap");
|
|
49
|
+
if (!node_fs_1.default.existsSync(providerDir)) {
|
|
50
|
+
node_fs_1.default.mkdirSync(providerDir, { recursive: true });
|
|
51
|
+
}
|
|
52
|
+
const providerPath = node_path_1.default.join(providerDir, "IDBProvider.tsx");
|
|
53
|
+
const providerContent = `"use client"
|
|
54
|
+
|
|
55
|
+
import { useEffect } from "react"
|
|
56
|
+
import { idb } from "@skalfa/skalfa-idb"
|
|
57
|
+
import { AppSchema } from "@schema"
|
|
58
|
+
import { registry } from "@utils"
|
|
59
|
+
|
|
60
|
+
export function IDBProvider({ children }: { children: React.ReactNode }) {
|
|
61
|
+
useEffect(() => {
|
|
62
|
+
idb.setDefaultSchema(AppSchema);
|
|
63
|
+
registry.register("idb", idb);
|
|
64
|
+
}, []);
|
|
65
|
+
|
|
66
|
+
return <>{children}</>
|
|
67
|
+
}
|
|
68
|
+
`;
|
|
69
|
+
node_fs_1.default.writeFileSync(providerPath, providerContent, "utf8");
|
|
70
|
+
console.log(`Created: ${providerPath}`);
|
|
71
|
+
// Scaffold app.schema.ts
|
|
72
|
+
console.log("Scaffolding AppSchema...");
|
|
73
|
+
const schemaDir = node_path_1.default.join(projectRoot, "schema", "idb");
|
|
74
|
+
if (!node_fs_1.default.existsSync(schemaDir)) {
|
|
75
|
+
node_fs_1.default.mkdirSync(schemaDir, { recursive: true });
|
|
76
|
+
}
|
|
77
|
+
const schemaPath = node_path_1.default.join(schemaDir, "app.schema.ts");
|
|
78
|
+
const schemaContent = `import { DBSchema } from "@skalfa/skalfa-idb"
|
|
79
|
+
|
|
80
|
+
const name = String(process.env.NEXT_PUBLIC_APP_NAME || "").toLowerCase().trim().replace(/[^\\w\\s-]/g, "").replace(/[\\s_-]+/g, "-").replace(/^-+|-+$/g, "") + ".idb-app";
|
|
81
|
+
|
|
82
|
+
export const AppSchema: DBSchema = {
|
|
83
|
+
name: name,
|
|
84
|
+
version: 1,
|
|
85
|
+
stores: {}
|
|
86
|
+
}
|
|
87
|
+
`;
|
|
88
|
+
node_fs_1.default.writeFileSync(schemaPath, schemaContent, "utf8");
|
|
89
|
+
console.log(`Created: ${schemaPath}`);
|
|
90
|
+
// Ensure schema/index.ts exists so the @schema alias works
|
|
91
|
+
const schemaIndexPath = node_path_1.default.join(projectRoot, "schema", "index.ts");
|
|
92
|
+
if (!node_fs_1.default.existsSync(schemaIndexPath)) {
|
|
93
|
+
node_fs_1.default.writeFileSync(schemaIndexPath, `export * from "./idb/app.schema";\n`, "utf8");
|
|
94
|
+
console.log(`Created: ${schemaIndexPath}`);
|
|
95
|
+
}
|
|
96
|
+
// Update app/layout.tsx
|
|
97
|
+
console.log("Updating app/layout.tsx...");
|
|
98
|
+
const layoutPath = node_path_1.default.join(projectRoot, "app", "layout.tsx");
|
|
99
|
+
if (node_fs_1.default.existsSync(layoutPath)) {
|
|
100
|
+
let layoutContent = node_fs_1.default.readFileSync(layoutPath, "utf8");
|
|
101
|
+
// 1. Add IDBProvider to import
|
|
102
|
+
if (layoutContent.includes('import { ShortcutProvider } from "@components";')) {
|
|
103
|
+
layoutContent = layoutContent.replace('import { ShortcutProvider } from "@components";', 'import { IDBProvider, ShortcutProvider } from "@components";');
|
|
104
|
+
}
|
|
105
|
+
else if (layoutContent.includes('import { ShortcutProvider } from "@components"')) {
|
|
106
|
+
layoutContent = layoutContent.replace('import { ShortcutProvider } from "@components"', 'import { IDBProvider, ShortcutProvider } from "@components"');
|
|
107
|
+
}
|
|
108
|
+
// 2. Wrap {children} with <IDBProvider>
|
|
109
|
+
if (layoutContent.includes("{children}") && !layoutContent.includes("<IDBProvider>")) {
|
|
110
|
+
layoutContent = layoutContent.replace(/\{\s*children\s*\}/, `<IDBProvider>\n {children}\n </IDBProvider>`);
|
|
111
|
+
}
|
|
112
|
+
node_fs_1.default.writeFileSync(layoutPath, layoutContent, "utf8");
|
|
113
|
+
console.log(`Updated: ${layoutPath}`);
|
|
114
|
+
}
|
|
46
115
|
}
|
|
47
116
|
else if (extensionName === "socket") {
|
|
48
117
|
console.log("Installing Skalfa Socket.io client extension...");
|
package/dist/commands/pick.js
CHANGED
|
@@ -5,6 +5,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
exports.UTILITIES = exports.UTILITY_EXPORTS = void 0;
|
|
7
7
|
exports.pickUtility = pickUtility;
|
|
8
|
+
exports.pickComponent = pickComponent;
|
|
8
9
|
const node_fs_1 = __importDefault(require("node:fs"));
|
|
9
10
|
const node_path_1 = __importDefault(require("node:path"));
|
|
10
11
|
const fs_1 = require("../utils/fs");
|
|
@@ -38,7 +39,6 @@ function pickUtility(utilityName) {
|
|
|
38
39
|
if (!(0, fs_1.exists)(utilsDir) || !(0, fs_1.exists)(indexPath)) {
|
|
39
40
|
throw new Error("Folder utils or utils/index.ts not found. Make sure you are at the project root.");
|
|
40
41
|
}
|
|
41
|
-
// 1. Tentukan path source dari node_modules dan target di lokal proyek
|
|
42
42
|
const corePackagePath = node_path_1.default.join(projectRoot, "node_modules", "@skalfa", "skalfa-api-core");
|
|
43
43
|
const sourceDir = node_path_1.default.join(corePackagePath, "src", utilityName);
|
|
44
44
|
const targetDir = node_path_1.default.join(utilsDir, utilityName);
|
|
@@ -48,7 +48,6 @@ function pickUtility(utilityName) {
|
|
|
48
48
|
if ((0, fs_1.exists)(targetDir)) {
|
|
49
49
|
throw new Error(`Utility folder "${utilityName}" is already present in your local utils folder.`);
|
|
50
50
|
}
|
|
51
|
-
// 2. Salin folder dari node_modules ke lokal proyek secara rekursif
|
|
52
51
|
console.log(`Copying ${utilityName} folder from @skalfa/skalfa-api-core to utils/ ...`);
|
|
53
52
|
node_fs_1.default.cpSync(sourceDir, targetDir, { recursive: true });
|
|
54
53
|
const filesToDelete = ["CONTRIBUTING.md", "LICENSE"];
|
|
@@ -59,7 +58,6 @@ function pickUtility(utilityName) {
|
|
|
59
58
|
}
|
|
60
59
|
}
|
|
61
60
|
console.log(`✓ Copied ${utilityName} folder`);
|
|
62
|
-
// 3. Perbarui utils/index.ts untuk mereferensikan folder lokal
|
|
63
61
|
console.log("Updating utils/index.ts with explicit local export override ...");
|
|
64
62
|
let indexContent = node_fs_1.default.readFileSync(indexPath, "utf8").trim();
|
|
65
63
|
const localExportLine = `export { ${utilitySymbols.join(", ")} } from "./${utilityName}";`;
|
|
@@ -73,3 +71,133 @@ function pickUtility(utilityName) {
|
|
|
73
71
|
}
|
|
74
72
|
console.log(`\nSuccess! You can now customize files under: utils/${utilityName}/`);
|
|
75
73
|
}
|
|
74
|
+
function findComponentFolder(srcDir, componentName) {
|
|
75
|
+
if (!node_fs_1.default.existsSync(srcDir))
|
|
76
|
+
return null;
|
|
77
|
+
const folders = node_fs_1.default.readdirSync(srcDir);
|
|
78
|
+
for (const folder of folders) {
|
|
79
|
+
const folderPath = node_path_1.default.join(srcDir, folder);
|
|
80
|
+
if (!node_fs_1.default.statSync(folderPath).isDirectory())
|
|
81
|
+
continue;
|
|
82
|
+
const files = node_fs_1.default.readdirSync(folderPath);
|
|
83
|
+
for (const file of files) {
|
|
84
|
+
if (!file.endsWith(".tsx") && !file.endsWith(".ts"))
|
|
85
|
+
continue;
|
|
86
|
+
const filePath = node_path_1.default.join(folderPath, file);
|
|
87
|
+
const content = node_fs_1.default.readFileSync(filePath, "utf8");
|
|
88
|
+
// Match the component name or the component name with "Component" suffix (e.g. Button or ButtonComponent)
|
|
89
|
+
const regex = new RegExp(`export\\s+(async\\s+)?(function|const|class|type|interface)\\s+\\b(${componentName}|${componentName}Component)\\b`);
|
|
90
|
+
if (regex.test(content)) {
|
|
91
|
+
return { folderName: folder, fileName: file };
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
return null;
|
|
96
|
+
}
|
|
97
|
+
function pickComponent(componentName, newName) {
|
|
98
|
+
const projectRoot = (0, fs_1.findProjectRoot)(process.cwd());
|
|
99
|
+
if (!projectRoot) {
|
|
100
|
+
throw new Error("No package.json found. Run this command inside a Skalfa project.");
|
|
101
|
+
}
|
|
102
|
+
// 1. Locate the component source in @skalfa/skalfa-component
|
|
103
|
+
let componentSrcDir = node_path_1.default.join(projectRoot, "node_modules", "@skalfa", "skalfa-component", "src");
|
|
104
|
+
if (!node_fs_1.default.existsSync(componentSrcDir)) {
|
|
105
|
+
componentSrcDir = node_path_1.default.resolve(projectRoot, "..", "skalfa-component", "src");
|
|
106
|
+
}
|
|
107
|
+
// Clean the componentName if it has "Component" suffix for searching
|
|
108
|
+
const cleanComponentName = componentName.endsWith("Component") ? componentName.replace(/Component$/, "") : componentName;
|
|
109
|
+
const match = findComponentFolder(componentSrcDir, cleanComponentName);
|
|
110
|
+
if (!match) {
|
|
111
|
+
throw new Error(`Component "${componentName}" not found in @skalfa/skalfa-component.`);
|
|
112
|
+
}
|
|
113
|
+
const { folderName, fileName } = match;
|
|
114
|
+
const sourceFolder = node_path_1.default.join(componentSrcDir, folderName);
|
|
115
|
+
const sourceFilePath = node_path_1.default.join(sourceFolder, fileName);
|
|
116
|
+
// Extract base name of the component file (e.g. "Button" from "Button.component.tsx")
|
|
117
|
+
const fileBaseName = fileName.replace(/\.(component)?\.(tsx|ts)$/, "");
|
|
118
|
+
// Find all exported symbols in the file that start with or match the file base name
|
|
119
|
+
const sourceContent = node_fs_1.default.readFileSync(sourceFilePath, "utf8");
|
|
120
|
+
const exportRegex = new RegExp(`export\\s+(async\\s+)?(function|const|class|type|interface)\\s+\\b(${fileBaseName}\\w*)\\b`, "g");
|
|
121
|
+
const exportedTypes = [];
|
|
122
|
+
const exportedValues = [];
|
|
123
|
+
let exportMatch;
|
|
124
|
+
while ((exportMatch = exportRegex.exec(sourceContent)) !== null) {
|
|
125
|
+
const keyword = exportMatch[2];
|
|
126
|
+
const symbol = exportMatch[3];
|
|
127
|
+
if (keyword === "type" || keyword === "interface") {
|
|
128
|
+
exportedTypes.push(symbol);
|
|
129
|
+
}
|
|
130
|
+
else {
|
|
131
|
+
exportedValues.push(symbol);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
const componentsDir = node_path_1.default.join(projectRoot, "components");
|
|
135
|
+
const indexPath = node_path_1.default.join(componentsDir, "index.ts");
|
|
136
|
+
if (!node_fs_1.default.existsSync(componentsDir)) {
|
|
137
|
+
node_fs_1.default.mkdirSync(componentsDir, { recursive: true });
|
|
138
|
+
}
|
|
139
|
+
// Ensure index.ts exists
|
|
140
|
+
if (!node_fs_1.default.existsSync(indexPath)) {
|
|
141
|
+
node_fs_1.default.writeFileSync(indexPath, `export * from "@skalfa/skalfa-component";\n`, "utf8");
|
|
142
|
+
}
|
|
143
|
+
// Determine target names
|
|
144
|
+
const targetFolderName = newName ? newName.charAt(0).toLowerCase() + newName.slice(1) : folderName;
|
|
145
|
+
const targetFolder = node_path_1.default.join(componentsDir, targetFolderName);
|
|
146
|
+
if (node_fs_1.default.existsSync(targetFolder)) {
|
|
147
|
+
throw new Error(`Component folder "${targetFolderName}" already exists in components/.`);
|
|
148
|
+
}
|
|
149
|
+
console.log(`Copying component "${cleanComponentName}" from ${sourceFolder} to components/${targetFolderName} ...`);
|
|
150
|
+
node_fs_1.default.cpSync(sourceFolder, targetFolder, { recursive: true });
|
|
151
|
+
let finalFileBaseName = fileBaseName;
|
|
152
|
+
let finalExportedTypes = [...exportedTypes];
|
|
153
|
+
let finalExportedValues = [...exportedValues];
|
|
154
|
+
// If renaming is requested, perform renaming of files and contents
|
|
155
|
+
if (newName) {
|
|
156
|
+
const cleanNewName = newName.endsWith("Component") ? newName.replace(/Component$/, "") : newName;
|
|
157
|
+
finalFileBaseName = cleanNewName;
|
|
158
|
+
finalExportedTypes = exportedTypes.map(sym => sym.replace(new RegExp(fileBaseName, "g"), cleanNewName));
|
|
159
|
+
finalExportedValues = exportedValues.map(sym => sym.replace(new RegExp(fileBaseName, "g"), cleanNewName));
|
|
160
|
+
const files = node_fs_1.default.readdirSync(targetFolder);
|
|
161
|
+
for (const file of files) {
|
|
162
|
+
const filePath = node_path_1.default.join(targetFolder, file);
|
|
163
|
+
if (node_fs_1.default.statSync(filePath).isDirectory())
|
|
164
|
+
continue;
|
|
165
|
+
// Read file content and replace component names
|
|
166
|
+
let content = node_fs_1.default.readFileSync(filePath, "utf8");
|
|
167
|
+
// Replace fileBaseName (e.g. Button) with cleanNewName (e.g. MyButton)
|
|
168
|
+
const nameRegex = new RegExp(fileBaseName, "g");
|
|
169
|
+
content = content.replace(nameRegex, cleanNewName);
|
|
170
|
+
node_fs_1.default.writeFileSync(filePath, content, "utf8");
|
|
171
|
+
// Rename the file if it contains the original file base name
|
|
172
|
+
if (file.includes(fileBaseName)) {
|
|
173
|
+
const newFile = file.replace(fileBaseName, cleanNewName);
|
|
174
|
+
node_fs_1.default.renameSync(filePath, node_path_1.default.join(targetFolder, newFile));
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
// Update components/index.ts to export the local component explicitly
|
|
179
|
+
let indexContent = node_fs_1.default.readFileSync(indexPath, "utf8");
|
|
180
|
+
// Find the exact filename of the main component file inside the target folder
|
|
181
|
+
const targetFiles = node_fs_1.default.readdirSync(targetFolder);
|
|
182
|
+
const componentFile = targetFiles.find(f => f.includes(finalFileBaseName) && (f.endsWith(".tsx") || f.endsWith(".ts")));
|
|
183
|
+
if (componentFile) {
|
|
184
|
+
const componentBaseFile = componentFile.replace(/\.(tsx|ts)$/, "");
|
|
185
|
+
const valueExport = finalExportedValues.length > 0
|
|
186
|
+
? `export { ${finalExportedValues.join(", ")} } from "./${targetFolderName}/${componentBaseFile}";`
|
|
187
|
+
: "";
|
|
188
|
+
const typeExport = finalExportedTypes.length > 0
|
|
189
|
+
? `export type { ${finalExportedTypes.join(", ")} } from "./${targetFolderName}/${componentBaseFile}";`
|
|
190
|
+
: "";
|
|
191
|
+
const localExportLine = [valueExport, typeExport].filter(Boolean).join("\n");
|
|
192
|
+
if (!indexContent.includes(`./${targetFolderName}/`)) {
|
|
193
|
+
indexContent = indexContent.trim() + `\n${localExportLine}\n`;
|
|
194
|
+
node_fs_1.default.writeFileSync(indexPath, indexContent, "utf8");
|
|
195
|
+
console.log(`✓ Updated components/index.ts with local export for: ${[...finalExportedValues, ...finalExportedTypes].join(", ")}`);
|
|
196
|
+
}
|
|
197
|
+
else {
|
|
198
|
+
console.log(`⚠️ Info: Local export for "${targetFolderName}" already exists in components/index.ts`);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
const finalSymbols = [...finalExportedValues, ...finalExportedTypes];
|
|
202
|
+
console.log(`\nSuccess! Component "${finalSymbols.find((s) => !s.endsWith("Props")) || finalSymbols[0]}" is now available locally at components/${targetFolderName}/`);
|
|
203
|
+
}
|
package/package.json
CHANGED