kadence-lang 0.2.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/LICENSE +21 -0
- package/README.md +208 -0
- package/bin/kadence.js +806 -0
- package/package.json +64 -0
- package/src/compiler.js +2291 -0
- package/src/vite-plugin-kadence.js +39 -0
- package/stdlib/check-helpers.js +13 -0
- package/stdlib/check.js +57 -0
- package/stdlib/check.kade +21 -0
- package/stdlib/color-helpers.js +34 -0
- package/stdlib/color.js +60 -0
- package/stdlib/color.kade +24 -0
- package/stdlib/console.js +57 -0
- package/stdlib/console.kade +21 -0
- package/stdlib/crypto-helpers.js +17 -0
- package/stdlib/crypto.js +46 -0
- package/stdlib/crypto.kade +11 -0
- package/stdlib/datetime.js +68 -0
- package/stdlib/datetime.kade +32 -0
- package/stdlib/encoding.js +55 -0
- package/stdlib/encoding.kade +19 -0
- package/stdlib/env.js +46 -0
- package/stdlib/env.kade +11 -0
- package/stdlib/file.js +94 -0
- package/stdlib/file.kade +57 -0
- package/stdlib/html.js +65 -0
- package/stdlib/html.kade +29 -0
- package/stdlib/json.js +63 -0
- package/stdlib/json.kade +28 -0
- package/stdlib/list.js +109 -0
- package/stdlib/list.kade +75 -0
- package/stdlib/math.js +76 -0
- package/stdlib/math.kade +39 -0
- package/stdlib/network.js +66 -0
- package/stdlib/network.kade +38 -0
- package/stdlib/number.js +53 -0
- package/stdlib/number.kade +17 -0
- package/stdlib/object-helpers.js +44 -0
- package/stdlib/object.js +59 -0
- package/stdlib/object.kade +23 -0
- package/stdlib/path.js +58 -0
- package/stdlib/path.kade +23 -0
- package/stdlib/process-helpers.js +18 -0
- package/stdlib/process.js +62 -0
- package/stdlib/process.kade +29 -0
- package/stdlib/promise-helpers.js +21 -0
- package/stdlib/promise.js +54 -0
- package/stdlib/promise.kade +19 -0
- package/stdlib/random.js +82 -0
- package/stdlib/random.kade +46 -0
- package/stdlib/regex-helpers.js +18 -0
- package/stdlib/regex.js +46 -0
- package/stdlib/regex.kade +11 -0
- package/stdlib/stream.js +58 -0
- package/stdlib/stream.kade +22 -0
- package/stdlib/string-helpers.js +16 -0
- package/stdlib/string.js +101 -0
- package/stdlib/string.kade +66 -0
- package/stdlib/system.js +66 -0
- package/stdlib/system.kade +31 -0
- package/stdlib/test-helpers.js +18 -0
- package/stdlib/test.js +72 -0
- package/stdlib/test.kade +37 -0
- package/stdlib/url.js +50 -0
- package/stdlib/url.kade +14 -0
- package/stdlib/uuid.js +70 -0
- package/stdlib/uuid.kade +35 -0
package/bin/kadence.js
ADDED
|
@@ -0,0 +1,806 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
const fs = require("fs");
|
|
3
|
+
const path = require("path");
|
|
4
|
+
const readline = require("readline");
|
|
5
|
+
const { execSync } = require("child_process");
|
|
6
|
+
const { compile } = require("../src/compiler");
|
|
7
|
+
|
|
8
|
+
const VERSION = "0.2.0";
|
|
9
|
+
|
|
10
|
+
const colors = {
|
|
11
|
+
reset: "\x1b[0m",
|
|
12
|
+
bright: "\x1b[1m",
|
|
13
|
+
green: "\x1b[32m",
|
|
14
|
+
blue: "\x1b[34m",
|
|
15
|
+
red: "\x1b[31m",
|
|
16
|
+
cyan: "\x1b[36m",
|
|
17
|
+
yellow: "\x1b[33m",
|
|
18
|
+
dim: "\x1b[2m",
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
function log(msg, color = colors.reset) {
|
|
22
|
+
console.log(`${color}${msg}${colors.reset}`);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/** Normalize package spec to name (e.g. @scope/pkg@1.0.0 -> @scope/pkg, lodash@1.0.0 -> lodash). */
|
|
26
|
+
function getPkgName(pkg) {
|
|
27
|
+
if (!pkg) return "";
|
|
28
|
+
if (pkg.startsWith("@")) {
|
|
29
|
+
const parts = pkg.split("@");
|
|
30
|
+
return parts.length >= 3 ? "@" + parts[1] + "/" + parts[2] : pkg;
|
|
31
|
+
}
|
|
32
|
+
return pkg.split("@")[0];
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function printHelp() {
|
|
36
|
+
log(`Kadence CLI v${VERSION}`, colors.bright + colors.cyan);
|
|
37
|
+
console.log(`
|
|
38
|
+
Usage:
|
|
39
|
+
kadence Start interactive REPL
|
|
40
|
+
kadence <file> Compile and run a .kade file
|
|
41
|
+
kadence -c <file> Compile to JavaScript only
|
|
42
|
+
kadence -c <file> -o <out> --target browser Compile for browser (no Node APIs)
|
|
43
|
+
kadence -c <file> -o <out> --sourcemap Emit source map for debugging
|
|
44
|
+
kadence create <name> [--web] [--yes] Create project folder and run npm install
|
|
45
|
+
kadence init [--web] Initialize a new project in current directory
|
|
46
|
+
kadence install <pkg> Install a package (npm/github)
|
|
47
|
+
kadence uninstall <pkg> Remove a package
|
|
48
|
+
kadence update Update all dependencies
|
|
49
|
+
kadence list List installed packages
|
|
50
|
+
kadence run <script> Run a script from kadence.json
|
|
51
|
+
kadence build Compile all .kade files in project
|
|
52
|
+
kadence test Run all .test.kade files
|
|
53
|
+
kadence docs Generate documentation from comments
|
|
54
|
+
kadence dev Start Vite development server
|
|
55
|
+
kadence -v Show version
|
|
56
|
+
kadence help Show this help
|
|
57
|
+
`);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function compileFile(filename, opts = {}) {
|
|
61
|
+
const fullPath = path.resolve(filename);
|
|
62
|
+
if (!fs.existsSync(fullPath)) return false;
|
|
63
|
+
const source = fs.readFileSync(fullPath, "utf8").trim();
|
|
64
|
+
try {
|
|
65
|
+
const result = compile(source, Object.assign({ target: "node", sourceFile: path.basename(fullPath) }, opts));
|
|
66
|
+
const js = typeof result === "string" ? result : result.code;
|
|
67
|
+
const map = typeof result === "object" ? result.map : null;
|
|
68
|
+
|
|
69
|
+
let outPath = opts.outputFile;
|
|
70
|
+
if (!outPath) {
|
|
71
|
+
if (opts.outputDir) {
|
|
72
|
+
const rel = path.relative(opts.baseDir || process.cwd(), fullPath);
|
|
73
|
+
outPath = path.join(opts.outputDir, rel.replace(/\.kade$/, ".js"));
|
|
74
|
+
const outDir = path.dirname(outPath);
|
|
75
|
+
if (!fs.existsSync(outDir)) fs.mkdirSync(outDir, { recursive: true });
|
|
76
|
+
} else {
|
|
77
|
+
outPath = fullPath.replace(/\.kade$/, ".js");
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
let toWrite = js;
|
|
82
|
+
if (opts.sourcemap && map) {
|
|
83
|
+
const mapPath = outPath + ".map";
|
|
84
|
+
fs.writeFileSync(mapPath, map, "utf8");
|
|
85
|
+
toWrite = js + "\n//# sourceMappingURL=" + path.basename(mapPath);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
fs.writeFileSync(outPath, toWrite, "utf8");
|
|
89
|
+
return true;
|
|
90
|
+
} catch (e) {
|
|
91
|
+
log(`Error in ${filename}: ${e.message}`, colors.red);
|
|
92
|
+
return false;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function buildProject(dir = process.cwd(), isRoot = true, opts = {}) {
|
|
97
|
+
if (isRoot) {
|
|
98
|
+
log(`Building project in ${dir}...`, colors.cyan);
|
|
99
|
+
if (fs.existsSync(path.join(dir, "kadence.json"))) {
|
|
100
|
+
const _config = JSON.parse(fs.readFileSync(path.join(dir, "kadence.json"), "utf8"));
|
|
101
|
+
// Default convention: src -> dist
|
|
102
|
+
opts.outputDir = opts.outputDir || path.join(dir, "dist");
|
|
103
|
+
opts.baseDir = opts.baseDir || path.join(dir, "src");
|
|
104
|
+
if (fs.existsSync(opts.baseDir)) {
|
|
105
|
+
dir = opts.baseDir;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const files = fs.readdirSync(dir);
|
|
111
|
+
let count = 0;
|
|
112
|
+
files.forEach((file) => {
|
|
113
|
+
const full = path.join(dir, file);
|
|
114
|
+
if (fs.statSync(full).isDirectory()) {
|
|
115
|
+
if (file !== "node_modules" && file !== "dist" && !file.startsWith(".")) {
|
|
116
|
+
count += buildProject(full, false, opts);
|
|
117
|
+
}
|
|
118
|
+
} else if (file.endsWith(".kade")) {
|
|
119
|
+
if (compileFile(full, opts)) {
|
|
120
|
+
log(` Compiled ${path.relative(process.cwd(), full)}`, colors.dim);
|
|
121
|
+
count++;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
});
|
|
125
|
+
if (isRoot) log(`Done. Compiled ${count} files.`, colors.green);
|
|
126
|
+
return count;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/** Write project scaffold into targetDir. opts: { projectName, version, description, author, isWeb }. */
|
|
130
|
+
function writeScaffold(targetDir, opts) {
|
|
131
|
+
const { projectName, version, description, author, isWeb } = opts;
|
|
132
|
+
const dirs = ["src", "dist", "src/components", "src/lib", "src/assets"];
|
|
133
|
+
dirs.forEach((d) => {
|
|
134
|
+
const full = path.join(targetDir, d);
|
|
135
|
+
if (!fs.existsSync(full)) fs.mkdirSync(full, { recursive: true });
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
const config = {
|
|
139
|
+
name: projectName,
|
|
140
|
+
version: version || "1.0.0",
|
|
141
|
+
description: description || "",
|
|
142
|
+
author: author || "",
|
|
143
|
+
scripts: isWeb
|
|
144
|
+
? { dev: "kadence dev", build: "kadence build --web", preview: "vite preview" }
|
|
145
|
+
: { start: "kadence src/main.kade", build: "kadence build" },
|
|
146
|
+
dependencies: {},
|
|
147
|
+
};
|
|
148
|
+
|
|
149
|
+
const mainKade = path.join(targetDir, "src", "main.kade");
|
|
150
|
+
if (!fs.existsSync(mainKade)) {
|
|
151
|
+
fs.writeFileSync(mainKade, "// Welcome to your new Kadence project!\nsay \"Hello, World!\"\n", "utf8");
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
const gitignore = path.join(targetDir, ".gitignore");
|
|
155
|
+
if (!fs.existsSync(gitignore)) {
|
|
156
|
+
fs.writeFileSync(gitignore, "node_modules/\ndist/\n*.js\n*.js.map\n", "utf8");
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
const kadenceConfig = path.join(targetDir, "kadence.config.js");
|
|
160
|
+
if (!fs.existsSync(kadenceConfig)) {
|
|
161
|
+
fs.writeFileSync(
|
|
162
|
+
kadenceConfig,
|
|
163
|
+
"/** @type {import('kadence-lang').Config} */\nmodule.exports = {\n outDir: 'dist',\n srcDir: 'src',\n target: 'node'\n};\n",
|
|
164
|
+
"utf8"
|
|
165
|
+
);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
fs.writeFileSync(path.join(targetDir, "kadence.json"), JSON.stringify(config, null, 2));
|
|
169
|
+
|
|
170
|
+
const pkgPath = path.join(targetDir, "package.json");
|
|
171
|
+
if (!fs.existsSync(pkgPath)) {
|
|
172
|
+
const pkg = {
|
|
173
|
+
name: projectName,
|
|
174
|
+
version: version || "1.0.0",
|
|
175
|
+
type: "commonjs",
|
|
176
|
+
dependencies: { "kadence-lang": "latest" },
|
|
177
|
+
};
|
|
178
|
+
if (isWeb) {
|
|
179
|
+
pkg.devDependencies = { vite: "latest", "vite-plugin-kadence": "latest" };
|
|
180
|
+
}
|
|
181
|
+
fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2));
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
if (isWeb) {
|
|
185
|
+
const indexHtml = path.join(targetDir, "index.html");
|
|
186
|
+
if (!fs.existsSync(indexHtml)) {
|
|
187
|
+
fs.writeFileSync(
|
|
188
|
+
indexHtml,
|
|
189
|
+
`<!DOCTYPE html>
|
|
190
|
+
<html>
|
|
191
|
+
<head>
|
|
192
|
+
<title>${projectName}</title>
|
|
193
|
+
</head>
|
|
194
|
+
<body>
|
|
195
|
+
<div id="app"></div>
|
|
196
|
+
<script type="module" src="/src/main.kade"></script>
|
|
197
|
+
</body>
|
|
198
|
+
</html>`,
|
|
199
|
+
"utf8"
|
|
200
|
+
);
|
|
201
|
+
}
|
|
202
|
+
const viteConfig = path.join(targetDir, "vite.config.js");
|
|
203
|
+
if (!fs.existsSync(viteConfig)) {
|
|
204
|
+
fs.writeFileSync(
|
|
205
|
+
viteConfig,
|
|
206
|
+
`import { defineConfig } from 'vite';
|
|
207
|
+
import { kadencePlugin } from './vite-plugin-kadence';
|
|
208
|
+
|
|
209
|
+
export default defineConfig({
|
|
210
|
+
plugins: [kadencePlugin()]
|
|
211
|
+
});`,
|
|
212
|
+
"utf8"
|
|
213
|
+
);
|
|
214
|
+
}
|
|
215
|
+
const pluginSrc = path.join(__dirname, "../src/vite-plugin-kadence.js");
|
|
216
|
+
const pluginDest = path.join(targetDir, "vite-plugin-kadence.js");
|
|
217
|
+
if (fs.existsSync(pluginSrc) && !fs.existsSync(pluginDest)) {
|
|
218
|
+
fs.copyFileSync(pluginSrc, pluginDest);
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
function initProject() {
|
|
224
|
+
const rl = readline.createInterface({
|
|
225
|
+
input: process.stdin,
|
|
226
|
+
output: process.stdout,
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
log("Initialize Kadence Project", colors.bright + colors.cyan);
|
|
230
|
+
|
|
231
|
+
rl.question(`Project name (${path.basename(process.cwd())}): `, (name) => {
|
|
232
|
+
const projectName = name || path.basename(process.cwd());
|
|
233
|
+
rl.question("Version (1.0.0): ", (ver) => {
|
|
234
|
+
const version = ver || "1.0.0";
|
|
235
|
+
rl.question("Description: ", (desc) => {
|
|
236
|
+
const description = desc || "";
|
|
237
|
+
rl.question("Author: ", (author) => {
|
|
238
|
+
const isWeb = process.argv.includes("--web");
|
|
239
|
+
writeScaffold(process.cwd(), {
|
|
240
|
+
projectName,
|
|
241
|
+
version,
|
|
242
|
+
description,
|
|
243
|
+
author: author || "",
|
|
244
|
+
isWeb,
|
|
245
|
+
});
|
|
246
|
+
log(`\n✨ Project '${projectName}' initialized.`, colors.green);
|
|
247
|
+
log(`Next steps:\n npm install\n ${isWeb ? "kadence dev" : "kadence run start"}`, colors.dim);
|
|
248
|
+
rl.close();
|
|
249
|
+
process.exit(0);
|
|
250
|
+
});
|
|
251
|
+
});
|
|
252
|
+
});
|
|
253
|
+
});
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
function createProject(appName) {
|
|
257
|
+
const isWeb = process.argv.includes("--web");
|
|
258
|
+
if (!appName || appName.startsWith("-")) {
|
|
259
|
+
log("Usage: kadence create <project-name> [--web] [--yes]", colors.red);
|
|
260
|
+
log("Example: kadence create my-app --web", colors.dim);
|
|
261
|
+
process.exit(1);
|
|
262
|
+
}
|
|
263
|
+
const targetDir = path.resolve(process.cwd(), appName);
|
|
264
|
+
if (fs.existsSync(targetDir)) {
|
|
265
|
+
const stat = fs.statSync(targetDir);
|
|
266
|
+
if (stat.isDirectory() && fs.readdirSync(targetDir).length > 0) {
|
|
267
|
+
log(`Error: Directory '${appName}' already exists and is not empty.`, colors.red);
|
|
268
|
+
process.exit(1);
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
log(`Creating Kadence project '${appName}'...`, colors.cyan);
|
|
272
|
+
fs.mkdirSync(targetDir, { recursive: true });
|
|
273
|
+
writeScaffold(targetDir, {
|
|
274
|
+
projectName: appName,
|
|
275
|
+
version: "1.0.0",
|
|
276
|
+
description: "",
|
|
277
|
+
author: "",
|
|
278
|
+
isWeb,
|
|
279
|
+
});
|
|
280
|
+
log(`✨ Project created. Installing dependencies...`, colors.green);
|
|
281
|
+
try {
|
|
282
|
+
execSync("npm install", { cwd: targetDir, stdio: "inherit" });
|
|
283
|
+
} catch (_e) {
|
|
284
|
+
log("npm install failed. Run 'npm install' inside the project manually.", colors.yellow);
|
|
285
|
+
}
|
|
286
|
+
log(`\nNext steps:`, colors.bright);
|
|
287
|
+
log(` cd ${appName}`, colors.cyan);
|
|
288
|
+
log(` ${isWeb ? "npm run dev" : "npm run start"}`, colors.cyan);
|
|
289
|
+
process.exit(0);
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
function installPackage(pkg) {
|
|
293
|
+
if (!pkg) {
|
|
294
|
+
// If no package specified, install all from kadence.json
|
|
295
|
+
if (fs.existsSync("kadence.json")) {
|
|
296
|
+
const config = JSON.parse(fs.readFileSync("kadence.json", "utf8"));
|
|
297
|
+
const deps = Object.keys(config.dependencies || {});
|
|
298
|
+
if (deps.length === 0) {
|
|
299
|
+
log("No dependencies to install.", colors.yellow);
|
|
300
|
+
process.exit(0);
|
|
301
|
+
}
|
|
302
|
+
log(`Installing ${deps.length} dependencies...`, colors.cyan);
|
|
303
|
+
deps.forEach((d) => {
|
|
304
|
+
try {
|
|
305
|
+
execSync(`npm install ${d}`, { stdio: "inherit" });
|
|
306
|
+
} catch (_e) {
|
|
307
|
+
log(`Failed to install ${d}`, colors.red);
|
|
308
|
+
}
|
|
309
|
+
});
|
|
310
|
+
log("All dependencies installed.", colors.green);
|
|
311
|
+
process.exit(0);
|
|
312
|
+
} else {
|
|
313
|
+
log("Usage: kadence install <package_name>", colors.red);
|
|
314
|
+
log("Or run 'kadence init' first to create kadence.json", colors.dim);
|
|
315
|
+
process.exit(1);
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
log(`Installing ${pkg}...`, colors.cyan);
|
|
320
|
+
|
|
321
|
+
// Initialize package.json if not exists
|
|
322
|
+
if (!fs.existsSync("package.json")) {
|
|
323
|
+
execSync("npm init -y", { stdio: "ignore" });
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
try {
|
|
327
|
+
execSync(`npm install ${pkg}`, { stdio: "inherit" });
|
|
328
|
+
|
|
329
|
+
// Get actual installed version from package.json
|
|
330
|
+
const pkgName = getPkgName(pkg);
|
|
331
|
+
let installedVersion = "latest";
|
|
332
|
+
if (fs.existsSync("package.json")) {
|
|
333
|
+
const npmPkg = JSON.parse(fs.readFileSync("package.json", "utf8"));
|
|
334
|
+
if (npmPkg.dependencies && npmPkg.dependencies[pkgName]) {
|
|
335
|
+
installedVersion = npmPkg.dependencies[pkgName];
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
// Update kadence.json (create if missing)
|
|
340
|
+
let config;
|
|
341
|
+
if (fs.existsSync("kadence.json")) {
|
|
342
|
+
config = JSON.parse(fs.readFileSync("kadence.json", "utf8"));
|
|
343
|
+
} else {
|
|
344
|
+
config = {
|
|
345
|
+
name:
|
|
346
|
+
(fs.existsSync("package.json")
|
|
347
|
+
? JSON.parse(fs.readFileSync("package.json", "utf8")).name
|
|
348
|
+
: null) || path.basename(process.cwd()),
|
|
349
|
+
version:
|
|
350
|
+
(fs.existsSync("package.json")
|
|
351
|
+
? JSON.parse(fs.readFileSync("package.json", "utf8")).version
|
|
352
|
+
: null) || "1.0.0",
|
|
353
|
+
dependencies: {},
|
|
354
|
+
};
|
|
355
|
+
}
|
|
356
|
+
config.dependencies = config.dependencies || {};
|
|
357
|
+
config.dependencies[pkgName] = installedVersion;
|
|
358
|
+
fs.writeFileSync("kadence.json", JSON.stringify(config, null, 2));
|
|
359
|
+
log(`Added ${pkgName}@${installedVersion} to kadence.json`, colors.green);
|
|
360
|
+
|
|
361
|
+
// Auto-compile if it's a Kadence package
|
|
362
|
+
const pkgDir = path.join("node_modules", pkgName);
|
|
363
|
+
if (
|
|
364
|
+
fs.existsSync(path.join(pkgDir, "kadence.json")) ||
|
|
365
|
+
fs.readdirSync(pkgDir).some((f) => f.endsWith(".kade"))
|
|
366
|
+
) {
|
|
367
|
+
log(`Kadence package detected. Compiling...`, colors.blue);
|
|
368
|
+
buildProject(pkgDir, false);
|
|
369
|
+
}
|
|
370
|
+
} catch (_e) {
|
|
371
|
+
log(`Failed to install ${pkg}`, colors.red);
|
|
372
|
+
process.exit(1);
|
|
373
|
+
}
|
|
374
|
+
process.exit(0);
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
function uninstallPackage(pkg) {
|
|
378
|
+
if (!pkg) {
|
|
379
|
+
log("Usage: kadence uninstall <package_name>", colors.red);
|
|
380
|
+
process.exit(1);
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
log(`Uninstalling ${pkg}...`, colors.cyan);
|
|
384
|
+
|
|
385
|
+
try {
|
|
386
|
+
execSync(`npm uninstall ${pkg}`, { stdio: "inherit" });
|
|
387
|
+
|
|
388
|
+
const pkgName = getPkgName(pkg);
|
|
389
|
+
if (fs.existsSync("kadence.json")) {
|
|
390
|
+
const config = JSON.parse(fs.readFileSync("kadence.json", "utf8"));
|
|
391
|
+
if (config.dependencies && config.dependencies[pkgName]) {
|
|
392
|
+
delete config.dependencies[pkgName];
|
|
393
|
+
fs.writeFileSync("kadence.json", JSON.stringify(config, null, 2));
|
|
394
|
+
log(`Removed ${pkgName} from kadence.json`, colors.green);
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
process.exit(0);
|
|
398
|
+
} catch (_e) {
|
|
399
|
+
log(`Failed to uninstall ${pkg}`, colors.red);
|
|
400
|
+
process.exit(1);
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
function updatePackages() {
|
|
405
|
+
log("Updating all packages...", colors.cyan);
|
|
406
|
+
|
|
407
|
+
try {
|
|
408
|
+
execSync("npm update", { stdio: "inherit" });
|
|
409
|
+
|
|
410
|
+
// Sync versions back to kadence.json
|
|
411
|
+
if (fs.existsSync("kadence.json") && fs.existsSync("package.json")) {
|
|
412
|
+
const config = JSON.parse(fs.readFileSync("kadence.json", "utf8"));
|
|
413
|
+
const npmPkg = JSON.parse(fs.readFileSync("package.json", "utf8"));
|
|
414
|
+
|
|
415
|
+
for (const dep of Object.keys(config.dependencies || {})) {
|
|
416
|
+
if (npmPkg.dependencies && npmPkg.dependencies[dep]) {
|
|
417
|
+
config.dependencies[dep] = npmPkg.dependencies[dep];
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
fs.writeFileSync("kadence.json", JSON.stringify(config, null, 2));
|
|
421
|
+
log("Updated kadence.json with new versions.", colors.green);
|
|
422
|
+
}
|
|
423
|
+
} catch (_e) {
|
|
424
|
+
log("Failed to update packages.", colors.red);
|
|
425
|
+
process.exit(1);
|
|
426
|
+
}
|
|
427
|
+
process.exit(0);
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
function listPackages() {
|
|
431
|
+
log("Installed Packages:", colors.bright + colors.cyan);
|
|
432
|
+
|
|
433
|
+
if (!fs.existsSync("kadence.json")) {
|
|
434
|
+
log("No kadence.json found. Run 'kadence init' first.", colors.yellow);
|
|
435
|
+
process.exit(0);
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
const config = JSON.parse(fs.readFileSync("kadence.json", "utf8"));
|
|
439
|
+
const deps = config.dependencies || {};
|
|
440
|
+
const depList = Object.entries(deps);
|
|
441
|
+
|
|
442
|
+
if (depList.length === 0) {
|
|
443
|
+
log(" (no dependencies)", colors.dim);
|
|
444
|
+
} else {
|
|
445
|
+
depList.forEach(([name, version]) => {
|
|
446
|
+
log(` ${name}: ${version}`, colors.reset);
|
|
447
|
+
});
|
|
448
|
+
}
|
|
449
|
+
process.exit(0);
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
function runScript(scriptName) {
|
|
453
|
+
if (!fs.existsSync("kadence.json")) {
|
|
454
|
+
log("No kadence.json found. Run 'kadence init' first.", colors.red);
|
|
455
|
+
process.exit(1);
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
const config = JSON.parse(fs.readFileSync("kadence.json", "utf8"));
|
|
459
|
+
const scripts = config.scripts || {};
|
|
460
|
+
|
|
461
|
+
if (!scriptName) {
|
|
462
|
+
log("Available scripts:", colors.cyan);
|
|
463
|
+
const scriptList = Object.keys(scripts);
|
|
464
|
+
if (scriptList.length === 0) {
|
|
465
|
+
log(" (no scripts defined)", colors.dim);
|
|
466
|
+
} else {
|
|
467
|
+
scriptList.forEach((s) => log(` ${s}: ${scripts[s]}`, colors.reset));
|
|
468
|
+
}
|
|
469
|
+
process.exit(0);
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
if (!scripts[scriptName]) {
|
|
473
|
+
log(`Script '${scriptName}' not found in kadence.json`, colors.red);
|
|
474
|
+
process.exit(1);
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
const scriptFile = scripts[scriptName];
|
|
478
|
+
log(`Running ${scriptName}: ${scriptFile}`, colors.blue);
|
|
479
|
+
|
|
480
|
+
try {
|
|
481
|
+
// If it's a .kade file, run it with kadence
|
|
482
|
+
if (scriptFile.endsWith(".kade")) {
|
|
483
|
+
execSync(`node "${__filename}" "${scriptFile}"`, { stdio: "inherit" });
|
|
484
|
+
} else {
|
|
485
|
+
execSync(scriptFile, { stdio: "inherit", shell: true });
|
|
486
|
+
}
|
|
487
|
+
} catch (_e) {
|
|
488
|
+
process.exit(1);
|
|
489
|
+
}
|
|
490
|
+
process.exit(0);
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
/**
|
|
494
|
+
* REPL with history, context persistence, and basic completion.
|
|
495
|
+
*/
|
|
496
|
+
function startRepl() {
|
|
497
|
+
const vm = require("vm");
|
|
498
|
+
const repl = require("repl");
|
|
499
|
+
|
|
500
|
+
log(`Kadence ${VERSION} ♪`, colors.bright + colors.green);
|
|
501
|
+
log(`Type '.exit' to quit. Context is preserved.`, colors.dim);
|
|
502
|
+
console.log("");
|
|
503
|
+
|
|
504
|
+
// Create a persistent context for the session
|
|
505
|
+
const context = vm.createContext({
|
|
506
|
+
require: require,
|
|
507
|
+
console: console,
|
|
508
|
+
process: process,
|
|
509
|
+
exports: {},
|
|
510
|
+
module: { exports: {} },
|
|
511
|
+
__kadence_echo: (val) => { console.log("\x1b[32m" + String(val) + "\x1b[0m"); },
|
|
512
|
+
__kadence_add: (p, c) => Array.isArray(p) ? p.push(c) : p.appendChild(c),
|
|
513
|
+
__kadence_min: (v) => Array.isArray(v) ? Math.min(...v) : v,
|
|
514
|
+
__kadence_max: (v) => Array.isArray(v) ? Math.max(...v) : v,
|
|
515
|
+
fs: require('fs')
|
|
516
|
+
});
|
|
517
|
+
|
|
518
|
+
// Custom eval function that compiles Kadence -> JS -> runs in VM
|
|
519
|
+
function kadenceEval(cmd, _context, _filename, callback) {
|
|
520
|
+
const input = cmd.trim();
|
|
521
|
+
if (!input) return callback(null);
|
|
522
|
+
|
|
523
|
+
// Filter out REPL commands
|
|
524
|
+
if (input.startsWith(".")) {
|
|
525
|
+
// Allow default REPL commands to handle it if we return undefined?
|
|
526
|
+
// Actually standard Node REPL handles .exit etc before this.
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
try {
|
|
530
|
+
// Compile snippet to JS body
|
|
531
|
+
// We wrap it in an async IIFE to support top-level await if needed
|
|
532
|
+
// But for variables to persist, we need to strip 'let'/'const' maybe?
|
|
533
|
+
// Or just compile it as a program.
|
|
534
|
+
|
|
535
|
+
// Attempt to compile
|
|
536
|
+
const compiled = compile(input, { target: "node", sourceFile: "repl" });
|
|
537
|
+
|
|
538
|
+
// The compiler outputs a full program with "const fs = require..."
|
|
539
|
+
// We need to strip the preamble for REPL use to avoid redeclaration errors
|
|
540
|
+
// and allow "let x = 1" to stick in the global scope if possible.
|
|
541
|
+
|
|
542
|
+
let jsParams = compiled.replace(/const fs = require\("fs"\);/g, "")
|
|
543
|
+
.replace(/const \S+ = require\(.*\);/g, "") // Strip imports for now
|
|
544
|
+
.replace(/^\s*function __kadence_.+?}/gms, "") // Strip helpers
|
|
545
|
+
.replace(/^\(async \(\) => {\s*/, "") // Strip wrapper start
|
|
546
|
+
.replace(/\s*}\)\(\)\.catch\(.+?\);?\s*$/, ""); // Strip wrapper end
|
|
547
|
+
|
|
548
|
+
// Run in the persistent context
|
|
549
|
+
const result = vm.runInContext(jsParams, context);
|
|
550
|
+
callback(null, result);
|
|
551
|
+
} catch (e) {
|
|
552
|
+
if (e.message.includes("Unexpected end of input")) {
|
|
553
|
+
return callback(new repl.Recoverable(e));
|
|
554
|
+
}
|
|
555
|
+
callback(e);
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
const r = repl.start({
|
|
560
|
+
prompt: `${colors.cyan}kadence> ${colors.reset}`,
|
|
561
|
+
eval: kadenceEval,
|
|
562
|
+
writer: (output) => {
|
|
563
|
+
// format output nicely
|
|
564
|
+
if (output === undefined) return "";
|
|
565
|
+
return require("util").inspect(output, { colors: true });
|
|
566
|
+
},
|
|
567
|
+
useColors: true
|
|
568
|
+
});
|
|
569
|
+
|
|
570
|
+
// Expose context to REPL
|
|
571
|
+
Object.assign(r.context, context);
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
function runTests(dir = process.cwd()) {
|
|
575
|
+
if (dir === process.cwd()) log(`Running tests...`, colors.cyan);
|
|
576
|
+
|
|
577
|
+
const stats = { total: 0, passed: 0, failed: 0 };
|
|
578
|
+
const files = fs.readdirSync(dir);
|
|
579
|
+
|
|
580
|
+
files.forEach((file) => {
|
|
581
|
+
const full = path.join(dir, file);
|
|
582
|
+
if (fs.statSync(full).isDirectory()) {
|
|
583
|
+
if (file !== "node_modules" && file !== "dist" && !file.startsWith(".")) {
|
|
584
|
+
const subStats = runTests(full);
|
|
585
|
+
stats.total += subStats.total;
|
|
586
|
+
stats.passed += subStats.passed;
|
|
587
|
+
stats.failed += subStats.failed;
|
|
588
|
+
}
|
|
589
|
+
} else if (file.endsWith(".test.kade")) {
|
|
590
|
+
stats.total++;
|
|
591
|
+
log(`\n📄 ${path.relative(process.cwd(), full)}`, colors.bright);
|
|
592
|
+
const src = fs.readFileSync(full, "utf8");
|
|
593
|
+
try {
|
|
594
|
+
// Compile
|
|
595
|
+
const built = compile(src, { target: "node", sourceFile: file });
|
|
596
|
+
|
|
597
|
+
// Run
|
|
598
|
+
const tempFile = path.join(dir, `.${file}.js`);
|
|
599
|
+
fs.writeFileSync(tempFile, built);
|
|
600
|
+
|
|
601
|
+
try {
|
|
602
|
+
// We run synchronously to capture output in order
|
|
603
|
+
execSync(`node "${tempFile}"`, {
|
|
604
|
+
stdio: "inherit",
|
|
605
|
+
cwd: dir,
|
|
606
|
+
env: { ...process.env, KADENCE_TEST_MODE: "true" }
|
|
607
|
+
});
|
|
608
|
+
stats.passed++;
|
|
609
|
+
} catch (_err) {
|
|
610
|
+
stats.failed++;
|
|
611
|
+
// output already shown
|
|
612
|
+
} finally {
|
|
613
|
+
if (fs.existsSync(tempFile)) fs.unlinkSync(tempFile);
|
|
614
|
+
}
|
|
615
|
+
} catch (e) {
|
|
616
|
+
log(` Compilation Error: ${e.message}`, colors.red);
|
|
617
|
+
stats.failed++;
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
});
|
|
621
|
+
|
|
622
|
+
if (dir === process.cwd()) {
|
|
623
|
+
console.log("");
|
|
624
|
+
log("--- Test Summary ---", colors.bright);
|
|
625
|
+
if (stats.failed > 0) log(`Tests: ${stats.failed} failed, ${stats.passed} passed, ${stats.total} total`, colors.red);
|
|
626
|
+
else log(`Tests: ${stats.passed} passed, ${stats.total} total`, colors.green);
|
|
627
|
+
}
|
|
628
|
+
return stats;
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
function generateDocs(dir = process.cwd(), isRoot = true) {
|
|
632
|
+
if (isRoot) log(`Generating documentation...`, colors.cyan);
|
|
633
|
+
|
|
634
|
+
const files = fs.readdirSync(dir);
|
|
635
|
+
const docs = [];
|
|
636
|
+
|
|
637
|
+
files.forEach((file) => {
|
|
638
|
+
const full = path.join(dir, file);
|
|
639
|
+
if (fs.statSync(full).isDirectory()) {
|
|
640
|
+
if (file !== "node_modules" && file !== "dist" && !file.startsWith(".")) {
|
|
641
|
+
docs.push(...generateDocs(full, false));
|
|
642
|
+
}
|
|
643
|
+
} else if (file.endsWith(".kade")) {
|
|
644
|
+
const content = fs.readFileSync(full, "utf8");
|
|
645
|
+
const lines = content.split("\n");
|
|
646
|
+
let currentNote = "";
|
|
647
|
+
|
|
648
|
+
lines.forEach((line) => {
|
|
649
|
+
const trimmed = line.trim();
|
|
650
|
+
if (trimmed.startsWith("note:")) {
|
|
651
|
+
currentNote = trimmed.replace(/^note:\s*/, "").trim();
|
|
652
|
+
} else if (trimmed.startsWith("export function ") || trimmed.startsWith("export async function ")) {
|
|
653
|
+
const name = trimmed.replace(/^export (async )?function /, "").split(/[ ([]/)[0];
|
|
654
|
+
docs.push({ file: path.relative(process.cwd(), full), name, note: currentNote, type: "function" });
|
|
655
|
+
currentNote = "";
|
|
656
|
+
} else if (trimmed.startsWith("export const ") || trimmed.startsWith("export let ")) {
|
|
657
|
+
const name = trimmed.replace(/^export (const|let) /, "").split(/[ =]/)[0];
|
|
658
|
+
docs.push({ file: path.relative(process.cwd(), full), name, note: currentNote, type: "variable" });
|
|
659
|
+
currentNote = "";
|
|
660
|
+
} else if (trimmed.length > 0) {
|
|
661
|
+
// If a line is not a note and not an export, reset note if it was just a floating note
|
|
662
|
+
// But we keep it if it was immediately above
|
|
663
|
+
}
|
|
664
|
+
});
|
|
665
|
+
}
|
|
666
|
+
});
|
|
667
|
+
|
|
668
|
+
if (isRoot) {
|
|
669
|
+
if (docs.length === 0) {
|
|
670
|
+
log("No exported functions or variables found.", colors.yellow);
|
|
671
|
+
} else {
|
|
672
|
+
log("\n📖 API Documentation\n", colors.bright + colors.green);
|
|
673
|
+
let currentFile = "";
|
|
674
|
+
docs.forEach((d) => {
|
|
675
|
+
if (d.file !== currentFile) {
|
|
676
|
+
log(`\n📄 ${d.file}`, colors.blue + colors.bright);
|
|
677
|
+
currentFile = d.file;
|
|
678
|
+
}
|
|
679
|
+
const typeChar = d.type === "function" ? "ƒ" : "ν";
|
|
680
|
+
log(` ${colors.yellow}${typeChar} ${colors.reset}${colors.bright}${d.name}${colors.reset}${d.note ? " - " + colors.dim + d.note : ""}`);
|
|
681
|
+
});
|
|
682
|
+
console.log("");
|
|
683
|
+
}
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
return docs;
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
const args = process.argv.slice(2);
|
|
690
|
+
|
|
691
|
+
if (args.length === 0) {
|
|
692
|
+
startRepl();
|
|
693
|
+
} else if (args[0] === "help") {
|
|
694
|
+
printHelp();
|
|
695
|
+
} else if (args[0] === "-v") {
|
|
696
|
+
log(`Kadence ${VERSION}`, colors.green);
|
|
697
|
+
} else if (args[0] === "create") {
|
|
698
|
+
createProject(args[1]);
|
|
699
|
+
} else if (args[0] === "init") {
|
|
700
|
+
initProject();
|
|
701
|
+
} else if (args[0] === "install") {
|
|
702
|
+
installPackage(args[1]);
|
|
703
|
+
} else if (args[0] === "uninstall" || args[0] === "remove") {
|
|
704
|
+
uninstallPackage(args[1]);
|
|
705
|
+
} else if (args[0] === "update") {
|
|
706
|
+
updatePackages();
|
|
707
|
+
} else if (args[0] === "list" || args[0] === "ls") {
|
|
708
|
+
listPackages();
|
|
709
|
+
} else if (args[0] === "dev") {
|
|
710
|
+
try {
|
|
711
|
+
execSync("npx vite", { stdio: "inherit" });
|
|
712
|
+
} catch (_e) {
|
|
713
|
+
process.exit(1);
|
|
714
|
+
}
|
|
715
|
+
} else if (args[0] === "run") {
|
|
716
|
+
runScript(args[1]);
|
|
717
|
+
} else if (args[0] === "build") {
|
|
718
|
+
buildProject();
|
|
719
|
+
process.exit(0);
|
|
720
|
+
} else if (args[0] === "test") {
|
|
721
|
+
runTests();
|
|
722
|
+
process.exit(0);
|
|
723
|
+
} else if (args[0] === "docs") {
|
|
724
|
+
generateDocs();
|
|
725
|
+
process.exit(0);
|
|
726
|
+
} else {
|
|
727
|
+
let compileOnly = false;
|
|
728
|
+
let outputFile = null;
|
|
729
|
+
let filename = null;
|
|
730
|
+
let target = "node";
|
|
731
|
+
let sourcemap = false;
|
|
732
|
+
|
|
733
|
+
for (let i = 0; i < args.length; i++) {
|
|
734
|
+
if (args[i] === "-c") compileOnly = true;
|
|
735
|
+
else if (args[i] === "-o" && args[i + 1]) {
|
|
736
|
+
outputFile = args[i + 1];
|
|
737
|
+
i++;
|
|
738
|
+
} else if ((args[i] === "--target" || args[i] === "-t") && args[i + 1]) {
|
|
739
|
+
target = args[i + 1];
|
|
740
|
+
i++;
|
|
741
|
+
} else if (args[i] === "--sourcemap") sourcemap = true;
|
|
742
|
+
else if (!filename) filename = args[i];
|
|
743
|
+
}
|
|
744
|
+
|
|
745
|
+
if (!filename) {
|
|
746
|
+
log("Error: No file specified", colors.red);
|
|
747
|
+
process.exit(1);
|
|
748
|
+
}
|
|
749
|
+
|
|
750
|
+
const fullPath = path.resolve(filename);
|
|
751
|
+
if (!fs.existsSync(fullPath)) {
|
|
752
|
+
log(`Error: File not found: ${filename}`, colors.red);
|
|
753
|
+
process.exit(1);
|
|
754
|
+
}
|
|
755
|
+
|
|
756
|
+
const source = fs.readFileSync(fullPath, "utf8").trim();
|
|
757
|
+
|
|
758
|
+
try {
|
|
759
|
+
const result = compile(source, { target, sourcemap, sourceFile: path.basename(fullPath) });
|
|
760
|
+
const js = typeof result === "string" ? result : result.code;
|
|
761
|
+
const map = typeof result === "object" ? result.map : null;
|
|
762
|
+
|
|
763
|
+
if (outputFile) {
|
|
764
|
+
let toWrite = js;
|
|
765
|
+
const outPath = path.resolve(outputFile);
|
|
766
|
+
if (sourcemap && map) {
|
|
767
|
+
const mapPath = outPath + ".map";
|
|
768
|
+
fs.writeFileSync(mapPath, map, "utf8");
|
|
769
|
+
toWrite = js + "\n//# sourceMappingURL=" + path.basename(mapPath);
|
|
770
|
+
}
|
|
771
|
+
fs.writeFileSync(outPath, toWrite, "utf8");
|
|
772
|
+
log(`Successfully compiled to ${outputFile}`, colors.green);
|
|
773
|
+
} else if (compileOnly) {
|
|
774
|
+
console.log(js);
|
|
775
|
+
} else {
|
|
776
|
+
log(`--- Running ${path.basename(filename)} ---`, colors.blue);
|
|
777
|
+
|
|
778
|
+
const tempDir = path.dirname(fullPath);
|
|
779
|
+
const tempFile = path.join(tempDir, `.${path.basename(filename)}.js`);
|
|
780
|
+
let jsToRun = js;
|
|
781
|
+
if (sourcemap && map) {
|
|
782
|
+
const mapPath = tempFile + ".map";
|
|
783
|
+
fs.writeFileSync(mapPath, map, "utf8");
|
|
784
|
+
jsToRun = js + "\n//# sourceMappingURL=" + path.basename(mapPath);
|
|
785
|
+
}
|
|
786
|
+
fs.writeFileSync(tempFile, jsToRun, "utf8");
|
|
787
|
+
|
|
788
|
+
try {
|
|
789
|
+
const nodeCmd = sourcemap
|
|
790
|
+
? `node --enable-source-maps "${tempFile}"`
|
|
791
|
+
: `node "${tempFile}"`;
|
|
792
|
+
execSync(nodeCmd, { stdio: "inherit", cwd: tempDir });
|
|
793
|
+
} catch (_err) {
|
|
794
|
+
// Error is already printed by inherit stdio
|
|
795
|
+
} finally {
|
|
796
|
+
if (fs.existsSync(tempFile)) fs.unlinkSync(tempFile);
|
|
797
|
+
const tempMap = tempFile + ".map";
|
|
798
|
+
if (fs.existsSync(tempMap)) fs.unlinkSync(tempMap);
|
|
799
|
+
}
|
|
800
|
+
}
|
|
801
|
+
} catch (e) {
|
|
802
|
+
log("Kadence Error:", colors.red + colors.bright);
|
|
803
|
+
console.error(e.message);
|
|
804
|
+
process.exit(1);
|
|
805
|
+
}
|
|
806
|
+
}
|