kimchilang 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/workflows/ci.yml +66 -0
- package/README.md +1547 -0
- package/create-kimchi-app/README.md +44 -0
- package/create-kimchi-app/index.js +214 -0
- package/create-kimchi-app/package.json +22 -0
- package/editors/README.md +121 -0
- package/editors/sublime/KimchiLang.sublime-syntax +138 -0
- package/editors/vscode/README.md +90 -0
- package/editors/vscode/kimchilang-1.1.0.vsix +0 -0
- package/editors/vscode/language-configuration.json +37 -0
- package/editors/vscode/package.json +55 -0
- package/editors/vscode/src/extension.js +354 -0
- package/editors/vscode/syntaxes/kimchi.tmLanguage.json +215 -0
- package/examples/api/client.km +36 -0
- package/examples/async_pipe.km +58 -0
- package/examples/basic.kimchi +109 -0
- package/examples/cli_framework/README.md +92 -0
- package/examples/cli_framework/calculator.km +61 -0
- package/examples/cli_framework/deploy.km +126 -0
- package/examples/cli_framework/greeter.km +26 -0
- package/examples/config.static +27 -0
- package/examples/config.static.js +10 -0
- package/examples/env_test.km +37 -0
- package/examples/fibonacci.kimchi +17 -0
- package/examples/greeter.km +15 -0
- package/examples/hello.js +1 -0
- package/examples/hello.kimchi +3 -0
- package/examples/js_interop.km +42 -0
- package/examples/logger_example.km +34 -0
- package/examples/memo_fibonacci.km +17 -0
- package/examples/myapp/lib/http.js +14 -0
- package/examples/myapp/lib/http.km +16 -0
- package/examples/myapp/main.km +16 -0
- package/examples/myapp/main_with_mock.km +42 -0
- package/examples/myapp/services/api.js +18 -0
- package/examples/myapp/services/api.km +18 -0
- package/examples/new_features.kimchi +52 -0
- package/examples/project_example.static +20 -0
- package/examples/readme_examples.km +240 -0
- package/examples/reduce_pattern_match.km +85 -0
- package/examples/regex_match.km +46 -0
- package/examples/sample.js +45 -0
- package/examples/sample.km +39 -0
- package/examples/secrets.static +35 -0
- package/examples/secrets.static.js +30 -0
- package/examples/shell-example.mjs +144 -0
- package/examples/shell_example.km +19 -0
- package/examples/stdlib_test.km +22 -0
- package/examples/test_example.km +69 -0
- package/examples/testing/README.md +88 -0
- package/examples/testing/http_client.km +18 -0
- package/examples/testing/math.km +48 -0
- package/examples/testing/math.test.km +93 -0
- package/examples/testing/user_service.km +29 -0
- package/examples/testing/user_service.test.km +72 -0
- package/examples/use-config.mjs +141 -0
- package/examples/use_config.km +13 -0
- package/install.sh +59 -0
- package/package.json +29 -0
- package/pantry/acorn/index.km +1 -0
- package/pantry/is_number/index.km +1 -0
- package/pantry/is_odd/index.km +2 -0
- package/project.static +6 -0
- package/src/cli.js +1245 -0
- package/src/generator.js +1241 -0
- package/src/index.js +141 -0
- package/src/js2km.js +568 -0
- package/src/lexer.js +822 -0
- package/src/linter.js +810 -0
- package/src/package-manager.js +307 -0
- package/src/parser.js +1876 -0
- package/src/static-parser.js +500 -0
- package/src/typechecker.js +950 -0
- package/stdlib/array.km +0 -0
- package/stdlib/bitwise.km +38 -0
- package/stdlib/console.km +49 -0
- package/stdlib/date.km +97 -0
- package/stdlib/function.km +44 -0
- package/stdlib/http.km +197 -0
- package/stdlib/http.md +333 -0
- package/stdlib/index.km +26 -0
- package/stdlib/json.km +17 -0
- package/stdlib/logger.js +114 -0
- package/stdlib/logger.km +104 -0
- package/stdlib/math.km +120 -0
- package/stdlib/object.km +41 -0
- package/stdlib/promise.km +33 -0
- package/stdlib/string.km +93 -0
- package/stdlib/testing.md +265 -0
- package/test/test.js +599 -0
|
@@ -0,0 +1,307 @@
|
|
|
1
|
+
// KimchiLang Package Manager
|
|
2
|
+
// Handles fetching and managing external dependencies from GitHub
|
|
3
|
+
|
|
4
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync, rmSync } from 'fs';
|
|
5
|
+
import { resolve, join, dirname, basename } from 'path';
|
|
6
|
+
import { execSync } from 'child_process';
|
|
7
|
+
import { parseStaticFile } from './static-parser.js';
|
|
8
|
+
|
|
9
|
+
const DEPS_DIR = '.km_modules';
|
|
10
|
+
const LOCK_FILE = '.km_modules/.lock.json';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Parse project.static and extract dependencies
|
|
14
|
+
*/
|
|
15
|
+
export function parseProjectFile(projectPath = '.') {
|
|
16
|
+
const projectFile = resolve(projectPath, 'project.static');
|
|
17
|
+
|
|
18
|
+
if (!existsSync(projectFile)) {
|
|
19
|
+
return null;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const source = readFileSync(projectFile, 'utf-8');
|
|
23
|
+
const declarations = parseStaticFile(source, 'project');
|
|
24
|
+
|
|
25
|
+
return declarations;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Extract dependency URLs from project declarations
|
|
30
|
+
*/
|
|
31
|
+
export function getDependencies(declarations) {
|
|
32
|
+
if (!declarations || !declarations.depend) {
|
|
33
|
+
return [];
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const depend = declarations.depend;
|
|
37
|
+
|
|
38
|
+
if (depend.type !== 'array') {
|
|
39
|
+
throw new Error('depend must be an array of GitHub URLs');
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
return depend.values.map(v => {
|
|
43
|
+
if (v.type !== 'literal' || typeof v.value !== 'string') {
|
|
44
|
+
throw new Error('Each dependency must be a string URL');
|
|
45
|
+
}
|
|
46
|
+
return v.value;
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Parse a GitHub URL into owner, repo, and optional path/ref
|
|
52
|
+
* Supports formats:
|
|
53
|
+
* - github.com/owner/repo
|
|
54
|
+
* - github.com/owner/repo/path/to/module
|
|
55
|
+
* - github.com/owner/repo@tag
|
|
56
|
+
* - github.com/owner/repo/path@tag
|
|
57
|
+
*/
|
|
58
|
+
export function parseGitHubUrl(url) {
|
|
59
|
+
// Remove protocol if present
|
|
60
|
+
let cleanUrl = url.replace(/^https?:\/\//, '');
|
|
61
|
+
|
|
62
|
+
// Must start with github.com
|
|
63
|
+
if (!cleanUrl.startsWith('github.com/')) {
|
|
64
|
+
throw new Error(`Invalid GitHub URL: ${url}. Must start with github.com/`);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
cleanUrl = cleanUrl.replace('github.com/', '');
|
|
68
|
+
|
|
69
|
+
// Check for @ref
|
|
70
|
+
let ref = 'main';
|
|
71
|
+
if (cleanUrl.includes('@')) {
|
|
72
|
+
const [pathPart, refPart] = cleanUrl.split('@');
|
|
73
|
+
cleanUrl = pathPart;
|
|
74
|
+
ref = refPart;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const parts = cleanUrl.split('/');
|
|
78
|
+
if (parts.length < 2) {
|
|
79
|
+
throw new Error(`Invalid GitHub URL: ${url}. Must include owner/repo`);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const owner = parts[0];
|
|
83
|
+
const repo = parts[1];
|
|
84
|
+
const subpath = parts.slice(2).join('/') || '';
|
|
85
|
+
|
|
86
|
+
return { owner, repo, subpath, ref };
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Generate a unique directory name for a dependency
|
|
91
|
+
*/
|
|
92
|
+
export function getDependencyDirName(owner, repo, ref) {
|
|
93
|
+
const safeName = `${owner}_${repo}`;
|
|
94
|
+
if (ref && ref !== 'main' && ref !== 'master') {
|
|
95
|
+
return `${safeName}_${ref.replace(/[^a-zA-Z0-9]/g, '_')}`;
|
|
96
|
+
}
|
|
97
|
+
return safeName;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Fetch a dependency from GitHub
|
|
102
|
+
*/
|
|
103
|
+
export function fetchDependency(url, projectPath = '.') {
|
|
104
|
+
const { owner, repo, subpath, ref } = parseGitHubUrl(url);
|
|
105
|
+
const depsDir = resolve(projectPath, DEPS_DIR);
|
|
106
|
+
const dirName = getDependencyDirName(owner, repo, ref);
|
|
107
|
+
const targetDir = join(depsDir, dirName);
|
|
108
|
+
|
|
109
|
+
// Create deps directory if needed
|
|
110
|
+
if (!existsSync(depsDir)) {
|
|
111
|
+
mkdirSync(depsDir, { recursive: true });
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// Check if already exists
|
|
115
|
+
if (existsSync(targetDir)) {
|
|
116
|
+
console.log(` ✓ ${owner}/${repo} already installed`);
|
|
117
|
+
return { dirName, targetDir, subpath };
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
console.log(` ↓ Fetching ${owner}/${repo}@${ref}...`);
|
|
121
|
+
|
|
122
|
+
// Clone the repository
|
|
123
|
+
const cloneUrl = `https://github.com/${owner}/${repo}.git`;
|
|
124
|
+
|
|
125
|
+
try {
|
|
126
|
+
execSync(`git clone --depth 1 --branch ${ref} ${cloneUrl} ${targetDir}`, {
|
|
127
|
+
stdio: 'pipe',
|
|
128
|
+
cwd: projectPath,
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
// Remove .git directory to save space
|
|
132
|
+
const gitDir = join(targetDir, '.git');
|
|
133
|
+
if (existsSync(gitDir)) {
|
|
134
|
+
rmSync(gitDir, { recursive: true, force: true });
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
console.log(` ✓ Installed ${owner}/${repo}`);
|
|
138
|
+
} catch (error) {
|
|
139
|
+
// Try without branch (might be a tag)
|
|
140
|
+
try {
|
|
141
|
+
execSync(`git clone --depth 1 ${cloneUrl} ${targetDir}`, {
|
|
142
|
+
stdio: 'pipe',
|
|
143
|
+
cwd: projectPath,
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
// Checkout the specific ref
|
|
147
|
+
execSync(`git checkout ${ref}`, {
|
|
148
|
+
stdio: 'pipe',
|
|
149
|
+
cwd: targetDir,
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
// Remove .git directory
|
|
153
|
+
const gitDir = join(targetDir, '.git');
|
|
154
|
+
if (existsSync(gitDir)) {
|
|
155
|
+
rmSync(gitDir, { recursive: true, force: true });
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
console.log(` ✓ Installed ${owner}/${repo}@${ref}`);
|
|
159
|
+
} catch (innerError) {
|
|
160
|
+
throw new Error(`Failed to fetch ${url}: ${innerError.message}`);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
return { dirName, targetDir, subpath };
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Install all dependencies from project.static
|
|
169
|
+
*/
|
|
170
|
+
export function installDependencies(projectPath = '.') {
|
|
171
|
+
const declarations = parseProjectFile(projectPath);
|
|
172
|
+
|
|
173
|
+
if (!declarations) {
|
|
174
|
+
console.log('No project.static found');
|
|
175
|
+
return [];
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
const deps = getDependencies(declarations);
|
|
179
|
+
|
|
180
|
+
if (deps.length === 0) {
|
|
181
|
+
console.log('No dependencies declared');
|
|
182
|
+
return [];
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
console.log(`Installing ${deps.length} dependencies...\n`);
|
|
186
|
+
|
|
187
|
+
const installed = [];
|
|
188
|
+
|
|
189
|
+
for (const url of deps) {
|
|
190
|
+
try {
|
|
191
|
+
const result = fetchDependency(url, projectPath);
|
|
192
|
+
installed.push({ url, ...result });
|
|
193
|
+
} catch (error) {
|
|
194
|
+
console.error(` ✗ Failed to install ${url}: ${error.message}`);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// Write lock file
|
|
199
|
+
writeLockFile(projectPath, installed);
|
|
200
|
+
|
|
201
|
+
console.log(`\nInstalled ${installed.length}/${deps.length} dependencies`);
|
|
202
|
+
|
|
203
|
+
return installed;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* Write lock file with installed dependency info
|
|
208
|
+
*/
|
|
209
|
+
export function writeLockFile(projectPath, installed) {
|
|
210
|
+
const lockPath = resolve(projectPath, LOCK_FILE);
|
|
211
|
+
const lockDir = dirname(lockPath);
|
|
212
|
+
|
|
213
|
+
if (!existsSync(lockDir)) {
|
|
214
|
+
mkdirSync(lockDir, { recursive: true });
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
const lockData = {
|
|
218
|
+
version: 1,
|
|
219
|
+
installedAt: new Date().toISOString(),
|
|
220
|
+
dependencies: installed.map(dep => ({
|
|
221
|
+
url: dep.url,
|
|
222
|
+
dirName: dep.dirName,
|
|
223
|
+
subpath: dep.subpath,
|
|
224
|
+
})),
|
|
225
|
+
};
|
|
226
|
+
|
|
227
|
+
writeFileSync(lockPath, JSON.stringify(lockData, null, 2));
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* Read lock file
|
|
232
|
+
*/
|
|
233
|
+
export function readLockFile(projectPath = '.') {
|
|
234
|
+
const lockPath = resolve(projectPath, LOCK_FILE);
|
|
235
|
+
|
|
236
|
+
if (!existsSync(lockPath)) {
|
|
237
|
+
return null;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
return JSON.parse(readFileSync(lockPath, 'utf-8'));
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/**
|
|
244
|
+
* Resolve a module path to check if it's an external dependency
|
|
245
|
+
* Returns the file path if found in deps, null otherwise
|
|
246
|
+
*/
|
|
247
|
+
export function resolveExternalModule(modulePath, projectPath = '.') {
|
|
248
|
+
const lockData = readLockFile(projectPath);
|
|
249
|
+
|
|
250
|
+
if (!lockData || !lockData.dependencies) {
|
|
251
|
+
return null;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
const depsDir = resolve(projectPath, DEPS_DIR);
|
|
255
|
+
|
|
256
|
+
// Check each installed dependency
|
|
257
|
+
for (const dep of lockData.dependencies) {
|
|
258
|
+
const depDir = join(depsDir, dep.dirName);
|
|
259
|
+
|
|
260
|
+
// If subpath is specified, check if module path starts with a matching pattern
|
|
261
|
+
if (dep.subpath) {
|
|
262
|
+
// The module might be referenced by the subpath name
|
|
263
|
+
const subpathName = basename(dep.subpath);
|
|
264
|
+
if (modulePath.startsWith(subpathName + '.') || modulePath === subpathName) {
|
|
265
|
+
const relativePath = modulePath.replace(subpathName, '').replace(/^\./, '');
|
|
266
|
+
const fullPath = join(depDir, dep.subpath, relativePath.replace(/\./g, '/'));
|
|
267
|
+
|
|
268
|
+
// Try various extensions
|
|
269
|
+
for (const ext of ['.km', '.kimchi', '.kc', '/index.km', '/index.kimchi', '/index.kc']) {
|
|
270
|
+
const tryPath = fullPath + ext;
|
|
271
|
+
if (existsSync(tryPath)) {
|
|
272
|
+
return tryPath;
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
// Try matching by repo name
|
|
279
|
+
const repoName = dep.dirName.split('_')[1] || dep.dirName;
|
|
280
|
+
if (modulePath.startsWith(repoName + '.') || modulePath === repoName) {
|
|
281
|
+
const relativePath = modulePath.replace(repoName, '').replace(/^\./, '');
|
|
282
|
+
const fullPath = join(depDir, relativePath.replace(/\./g, '/'));
|
|
283
|
+
|
|
284
|
+
// Try various extensions
|
|
285
|
+
for (const ext of ['.km', '.kimchi', '.kc', '/index.km', '/index.kimchi', '/index.kc']) {
|
|
286
|
+
const tryPath = fullPath + ext;
|
|
287
|
+
if (existsSync(tryPath)) {
|
|
288
|
+
return tryPath;
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
return null;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
/**
|
|
298
|
+
* Clean installed dependencies
|
|
299
|
+
*/
|
|
300
|
+
export function cleanDependencies(projectPath = '.') {
|
|
301
|
+
const depsDir = resolve(projectPath, DEPS_DIR);
|
|
302
|
+
|
|
303
|
+
if (existsSync(depsDir)) {
|
|
304
|
+
rmSync(depsDir, { recursive: true, force: true });
|
|
305
|
+
console.log('Removed .km_modules');
|
|
306
|
+
}
|
|
307
|
+
}
|