@slim-lang/core 1.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/README.md +666 -0
- package/package.json +55 -0
- package/packages/slim/.spm +7 -0
- package/packages/slim/converters/main.slim +106 -0
- package/packages/slim/helpers/array.slim +25 -0
- package/packages/slim/helpers/path.slim +3 -0
- package/packages/slim/helpers/request.slim +102 -0
- package/packages/slim/helpers/string.slim +27 -0
- package/packages/slim/main.slim +42 -0
- package/packages/slim/parse/main.slim +25 -0
- package/packages/slim/server/main.slim +423 -0
- package/packages/slim/time/main.slim +66 -0
- package/packages/slim/types/common.slim +6 -0
- package/packages/slim/types/formats.slim +23 -0
- package/packages/slim/types/hash.slim +6 -0
- package/packages/slim/types/mails.slim +3 -0
- package/packages/slim/types/numerical.slim +9 -0
- package/packages/slim/types/time.slim +3 -0
- package/run-dev-slim.js +133 -0
- package/run-slim.js +20 -0
- package/src/bin/api/github_auth.js +89 -0
- package/src/bin/api/github_get.js +139 -0
- package/src/bin/api/github_req.js +455 -0
- package/src/bin/api/lock.js +37 -0
- package/src/bin/api/spm.js +103 -0
- package/src/bin/api/storage.js +30 -0
- package/src/bin/cli.js +404 -0
- package/src/bin/config.default.json +5 -0
- package/src/bin/helpers.js +147 -0
- package/src/bin/parsers/spm.js +174 -0
- package/src/bin/spm.js +519 -0
- package/src/checker.js +926 -0
- package/src/compile.js +230 -0
- package/src/external/classErrors.js +202 -0
- package/src/external/client.js +38 -0
- package/src/external/core.js +861 -0
- package/src/external/defaults.js +25 -0
- package/src/external/helpers.js +541 -0
- package/src/external/slim-globals.d.ts +65 -0
- package/src/external/types.js +38 -0
- package/src/format.js +81 -0
- package/src/handlers/errorHandler.js +43 -0
- package/src/handlers/parser/components.js +250 -0
- package/src/handlers/parserHandler.js +793 -0
- package/src/jsdoc.js +273 -0
- package/src/lexer.js +174 -0
- package/src/modulePaths.js +74 -0
- package/src/parser.js +818 -0
- package/src/repl.js +32 -0
- package/src/sourcemap.js +0 -0
- package/src/test-runner.js +62 -0
- package/src/transform.js +765 -0
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { formatBold, formatItalic } from "../helpers.js";
|
|
2
|
+
|
|
3
|
+
const CLIENT_ID = "Iv23lijjcVMtJwfS18dj";
|
|
4
|
+
|
|
5
|
+
const DEVICE_URL = "https://github.com/login/device/code";
|
|
6
|
+
const TOKEN_URL = "https://github.com/login/oauth/access_token";
|
|
7
|
+
|
|
8
|
+
export async function loginGitHub() {
|
|
9
|
+
if (!CLIENT_ID) {
|
|
10
|
+
throw new Error("GITHUB_CLIENT_ID is not configured");
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const response = await fetch(DEVICE_URL, {
|
|
14
|
+
method: "POST",
|
|
15
|
+
headers: {
|
|
16
|
+
"Accept": "application/json",
|
|
17
|
+
"Content-Type": "application/x-www-form-urlencoded"
|
|
18
|
+
},
|
|
19
|
+
body: new URLSearchParams({
|
|
20
|
+
client_id: CLIENT_ID
|
|
21
|
+
})
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
if (!response.ok) {
|
|
25
|
+
throw new Error("Failed to start GitHub authorization");
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const data = await response.json();
|
|
29
|
+
|
|
30
|
+
console.log("\n");
|
|
31
|
+
console.log("Open:", formatItalic.underline(data.verification_uri));
|
|
32
|
+
console.log(`Code: ${formatBold(data.user_code)}`);
|
|
33
|
+
console.log("");
|
|
34
|
+
console.log("Open the link and paste the code into the fields you see. Then, follow the steps");
|
|
35
|
+
|
|
36
|
+
let interval = data.interval || 5;
|
|
37
|
+
|
|
38
|
+
while (true) {
|
|
39
|
+
await sleep(interval * 1000);
|
|
40
|
+
|
|
41
|
+
const tokenResponse = await fetch(TOKEN_URL, {
|
|
42
|
+
method: "POST",
|
|
43
|
+
headers: {
|
|
44
|
+
"Accept": "application/json",
|
|
45
|
+
"Content-Type": "application/x-www-form-urlencoded"
|
|
46
|
+
},
|
|
47
|
+
body: new URLSearchParams({
|
|
48
|
+
client_id: CLIENT_ID,
|
|
49
|
+
device_code: data.device_code,
|
|
50
|
+
grant_type:
|
|
51
|
+
"urn:ietf:params:oauth:grant-type:device_code"
|
|
52
|
+
})
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
const tokenData = await tokenResponse.json();
|
|
56
|
+
|
|
57
|
+
if (tokenData.access_token) {
|
|
58
|
+
return tokenData.access_token;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (tokenData.error === "authorization_pending") {
|
|
62
|
+
continue;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
if (tokenData.error === "slow_down") {
|
|
66
|
+
interval += 5;
|
|
67
|
+
continue;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
if (tokenData.error === "access_denied") {
|
|
71
|
+
throw new Error("GitHub authorization denied");
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if (
|
|
75
|
+
tokenData.error === "expired_token"
|
|
76
|
+
) {
|
|
77
|
+
throw new Error("GitHub authorization expired");
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
throw new Error(
|
|
81
|
+
tokenData.error_description ||
|
|
82
|
+
"GitHub authorization failed"
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function sleep(ms) {
|
|
88
|
+
return new Promise(resolve => setTimeout(resolve, ms));
|
|
89
|
+
}
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import { GITHUB_API, githubHeaders } from "./github_req.js";
|
|
2
|
+
|
|
3
|
+
import fs from "node:fs";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import os from "node:os";
|
|
6
|
+
import { pipeline } from "node:stream/promises";
|
|
7
|
+
import { createWriteStream } from "node:fs";
|
|
8
|
+
import * as tar from 'tar'
|
|
9
|
+
|
|
10
|
+
export async function getGitHubUser(token) {
|
|
11
|
+
const response = await fetch(
|
|
12
|
+
`${GITHUB_API}/user`,
|
|
13
|
+
{
|
|
14
|
+
headers: githubHeaders(token)
|
|
15
|
+
}
|
|
16
|
+
);
|
|
17
|
+
|
|
18
|
+
if (!response.ok) {
|
|
19
|
+
throw new Error("GitHub token is invalid");
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
return response.json();
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export async function getGitHubRepo(token, repo) {
|
|
26
|
+
const response = await fetch(
|
|
27
|
+
`${GITHUB_API}/repos/${repo}`,
|
|
28
|
+
{
|
|
29
|
+
headers: githubHeaders(token)
|
|
30
|
+
}
|
|
31
|
+
);
|
|
32
|
+
|
|
33
|
+
if (!response.ok) {
|
|
34
|
+
throw new Error(
|
|
35
|
+
`GitHub repository "${repo}" not found`
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return response.json();
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export async function getPermission(token, repo, username) {
|
|
43
|
+
const response = await fetch(
|
|
44
|
+
`${GITHUB_API}/repos/${repo}/collaborators/${username}/permission`,
|
|
45
|
+
{
|
|
46
|
+
headers: githubHeaders(token)
|
|
47
|
+
}
|
|
48
|
+
);
|
|
49
|
+
|
|
50
|
+
if (!response.ok) {
|
|
51
|
+
throw new Error(
|
|
52
|
+
"Unable to verify repository permissions"
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return response.json();
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
async function extractTarGz(file, destination) {
|
|
60
|
+
await tar.x({
|
|
61
|
+
file,
|
|
62
|
+
cwd: destination,
|
|
63
|
+
strip: 1
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export async function downloadGitHubRepo(
|
|
68
|
+
token,
|
|
69
|
+
repository,
|
|
70
|
+
destination,
|
|
71
|
+
version = null
|
|
72
|
+
) {
|
|
73
|
+
const match = repository.match(
|
|
74
|
+
/^([a-zA-Z0-9-]+)\/([a-zA-Z0-9._-]+)$/
|
|
75
|
+
);
|
|
76
|
+
|
|
77
|
+
if (!match) {
|
|
78
|
+
return {
|
|
79
|
+
success: false,
|
|
80
|
+
msg: "Repository must have format user/repo"
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const [, owner, repo] = match;
|
|
85
|
+
|
|
86
|
+
try {
|
|
87
|
+
const ref = version
|
|
88
|
+
? `tarball/v${version}`
|
|
89
|
+
: "tarball/main";
|
|
90
|
+
|
|
91
|
+
const response = await fetch(
|
|
92
|
+
`${GITHUB_API}/repos/${owner}/${repo}/${ref}`,
|
|
93
|
+
{
|
|
94
|
+
headers: githubHeaders(token)
|
|
95
|
+
}
|
|
96
|
+
);
|
|
97
|
+
|
|
98
|
+
if (!response.ok) {
|
|
99
|
+
const data = await response.json().catch(() => null);
|
|
100
|
+
|
|
101
|
+
return {
|
|
102
|
+
success: false,
|
|
103
|
+
msg: data?.message || `GitHub API error: ${response.status}`
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const tempFile = path.join(
|
|
108
|
+
os.tmpdir(),
|
|
109
|
+
`spm-${owner}-${repo}-${Date.now()}.tar.gz`
|
|
110
|
+
);
|
|
111
|
+
|
|
112
|
+
await pipeline(
|
|
113
|
+
response.body,
|
|
114
|
+
createWriteStream(tempFile)
|
|
115
|
+
);
|
|
116
|
+
|
|
117
|
+
await fs.promises.mkdir(destination, {
|
|
118
|
+
recursive: true
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
await extractTarGz(tempFile, destination);
|
|
122
|
+
|
|
123
|
+
await fs.promises.unlink(tempFile);
|
|
124
|
+
|
|
125
|
+
return {
|
|
126
|
+
success: true,
|
|
127
|
+
msg: `Repository ${repository} downloaded`,
|
|
128
|
+
path: destination
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
catch (error) {
|
|
132
|
+
return {
|
|
133
|
+
success: false,
|
|
134
|
+
msg: error instanceof Error
|
|
135
|
+
? error.message
|
|
136
|
+
: String(error)
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
}
|
|
@@ -0,0 +1,455 @@
|
|
|
1
|
+
import fs from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
|
|
4
|
+
export const GITHUB_API = "https://api.github.com";
|
|
5
|
+
export const API_VERSION = "2026-03-10";
|
|
6
|
+
|
|
7
|
+
export function githubHeaders(token, extra = {}) {
|
|
8
|
+
return {
|
|
9
|
+
Accept: "application/vnd.github+json",
|
|
10
|
+
Authorization: `Bearer ${token}`,
|
|
11
|
+
"X-GitHub-Api-Version": API_VERSION,
|
|
12
|
+
...extra
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export async function githubRequest(token, url, options = {}) {
|
|
17
|
+
const response = await fetch(`${GITHUB_API}${url}`, {
|
|
18
|
+
...options,
|
|
19
|
+
headers: githubHeaders(token, {
|
|
20
|
+
"Content-Type": "application/json",
|
|
21
|
+
...options.headers
|
|
22
|
+
})
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
const data = await response.json();
|
|
26
|
+
|
|
27
|
+
if (!response.ok) {
|
|
28
|
+
throw new Error(
|
|
29
|
+
data.message || `GitHub API error: ${response.status}`
|
|
30
|
+
);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
return data;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
async function getFiles(dir, base = dir) {
|
|
37
|
+
const entries = await fs.readdir(dir, {
|
|
38
|
+
withFileTypes: true
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
const files = [];
|
|
42
|
+
|
|
43
|
+
for (const entry of entries) {
|
|
44
|
+
const fullPath = path.join(dir, entry.name);
|
|
45
|
+
|
|
46
|
+
if (entry.isDirectory()) {
|
|
47
|
+
files.push(
|
|
48
|
+
...(await getFiles(fullPath, base))
|
|
49
|
+
);
|
|
50
|
+
continue;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
if (!entry.isFile()) {
|
|
54
|
+
continue;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
files.push({
|
|
58
|
+
path: path.relative(base, fullPath).replaceAll("\\", "/"),
|
|
59
|
+
fullPath
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return files;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export async function publishToGitHub({
|
|
67
|
+
token,
|
|
68
|
+
repo,
|
|
69
|
+
contentPath,
|
|
70
|
+
branch = "main"
|
|
71
|
+
}) {
|
|
72
|
+
try {
|
|
73
|
+
const [owner, repoName] = repo.split("/");
|
|
74
|
+
|
|
75
|
+
if (!owner || !repoName || repo.split("/").length !== 2) {
|
|
76
|
+
return {
|
|
77
|
+
success: false,
|
|
78
|
+
msg: `Invalid GitHub repository: ${repo}`
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const repository = await githubRequest(
|
|
83
|
+
token,
|
|
84
|
+
`/repos/${owner}/${repoName}`
|
|
85
|
+
);
|
|
86
|
+
|
|
87
|
+
const files = await getFiles(contentPath);
|
|
88
|
+
|
|
89
|
+
if (!files.length) {
|
|
90
|
+
return {
|
|
91
|
+
success: false,
|
|
92
|
+
msg: "Package directory is empty"
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const reference = await githubRequest(
|
|
97
|
+
token,
|
|
98
|
+
`/repos/${owner}/${repoName}/git/ref/heads/${branch}`
|
|
99
|
+
);
|
|
100
|
+
|
|
101
|
+
const parentSha = reference.object.sha;
|
|
102
|
+
|
|
103
|
+
const parentCommit = await githubRequest(
|
|
104
|
+
token,
|
|
105
|
+
`/repos/${owner}/${repoName}/git/commits/${parentSha}`
|
|
106
|
+
);
|
|
107
|
+
|
|
108
|
+
const tree = [];
|
|
109
|
+
|
|
110
|
+
for (const file of files) {
|
|
111
|
+
const content = await fs.readFile(
|
|
112
|
+
file.fullPath,
|
|
113
|
+
"base64"
|
|
114
|
+
);
|
|
115
|
+
|
|
116
|
+
const blob = await githubRequest(
|
|
117
|
+
token,
|
|
118
|
+
`/repos/${owner}/${repoName}/git/blobs`,
|
|
119
|
+
{
|
|
120
|
+
method: "POST",
|
|
121
|
+
body: JSON.stringify({
|
|
122
|
+
content,
|
|
123
|
+
encoding: "base64"
|
|
124
|
+
})
|
|
125
|
+
}
|
|
126
|
+
);
|
|
127
|
+
|
|
128
|
+
tree.push({
|
|
129
|
+
path: file.path,
|
|
130
|
+
mode: "100644",
|
|
131
|
+
type: "blob",
|
|
132
|
+
sha: blob.sha
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
const newTree = await githubRequest(
|
|
137
|
+
token,
|
|
138
|
+
`/repos/${owner}/${repoName}/git/trees`,
|
|
139
|
+
{
|
|
140
|
+
method: "POST",
|
|
141
|
+
body: JSON.stringify({
|
|
142
|
+
base_tree: parentCommit.tree.sha,
|
|
143
|
+
tree
|
|
144
|
+
})
|
|
145
|
+
}
|
|
146
|
+
);
|
|
147
|
+
|
|
148
|
+
const commit = await githubRequest(
|
|
149
|
+
token,
|
|
150
|
+
`/repos/${owner}/${repoName}/git/commits`,
|
|
151
|
+
{
|
|
152
|
+
method: "POST",
|
|
153
|
+
body: JSON.stringify({
|
|
154
|
+
message: "Publish package",
|
|
155
|
+
tree: newTree.sha,
|
|
156
|
+
parents: [parentSha]
|
|
157
|
+
})
|
|
158
|
+
}
|
|
159
|
+
);
|
|
160
|
+
|
|
161
|
+
await githubRequest(
|
|
162
|
+
token,
|
|
163
|
+
`/repos/${owner}/${repoName}/git/refs/heads/${branch}`,
|
|
164
|
+
{
|
|
165
|
+
method: "PATCH",
|
|
166
|
+
body: JSON.stringify({
|
|
167
|
+
sha: commit.sha
|
|
168
|
+
})
|
|
169
|
+
}
|
|
170
|
+
);
|
|
171
|
+
|
|
172
|
+
return {
|
|
173
|
+
success: true,
|
|
174
|
+
msg: `Published ${repo}`,
|
|
175
|
+
repo: repository.full_name,
|
|
176
|
+
commit: commit.sha
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
catch (error) {
|
|
180
|
+
return {
|
|
181
|
+
success: false,
|
|
182
|
+
msg: error instanceof Error
|
|
183
|
+
? error.message
|
|
184
|
+
: String(error)
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
export async function updateGitHubRepo({
|
|
190
|
+
token,
|
|
191
|
+
repo,
|
|
192
|
+
packagePath,
|
|
193
|
+
version,
|
|
194
|
+
message = `Release ${version}`
|
|
195
|
+
}) {
|
|
196
|
+
const branch = await githubRequest(
|
|
197
|
+
token,
|
|
198
|
+
`/repos/${repo}/git/ref/heads/main`
|
|
199
|
+
);
|
|
200
|
+
|
|
201
|
+
const parentSha = branch.object.sha;
|
|
202
|
+
|
|
203
|
+
const parentCommit = await githubRequest(
|
|
204
|
+
token,
|
|
205
|
+
`/repos/${repo}/git/commits/${parentSha}`
|
|
206
|
+
);
|
|
207
|
+
|
|
208
|
+
const baseTreeSha = parentCommit.tree.sha;
|
|
209
|
+
|
|
210
|
+
const files = await getFiles(packagePath);
|
|
211
|
+
|
|
212
|
+
if (files.length === 0) {
|
|
213
|
+
throw new Error("Package directory is empty");
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
const tree = [];
|
|
217
|
+
|
|
218
|
+
for (const file of files) {
|
|
219
|
+
const content = await fs.readFile(file.fullPath);
|
|
220
|
+
|
|
221
|
+
const blob = await githubRequest(
|
|
222
|
+
token,
|
|
223
|
+
`/repos/${repo}/git/blobs`,
|
|
224
|
+
{
|
|
225
|
+
method: "POST",
|
|
226
|
+
body: JSON.stringify({
|
|
227
|
+
content: content.toString("base64"),
|
|
228
|
+
encoding: "base64"
|
|
229
|
+
})
|
|
230
|
+
}
|
|
231
|
+
);
|
|
232
|
+
|
|
233
|
+
tree.push({
|
|
234
|
+
path: file.path,
|
|
235
|
+
mode: "100644",
|
|
236
|
+
type: "blob",
|
|
237
|
+
sha: blob.sha
|
|
238
|
+
});
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
const newTree = await githubRequest(
|
|
242
|
+
token,
|
|
243
|
+
`/repos/${repo}/git/trees`,
|
|
244
|
+
{
|
|
245
|
+
method: "POST",
|
|
246
|
+
body: JSON.stringify({
|
|
247
|
+
base_tree: baseTreeSha,
|
|
248
|
+
tree
|
|
249
|
+
})
|
|
250
|
+
}
|
|
251
|
+
);
|
|
252
|
+
|
|
253
|
+
const commit = await githubRequest(
|
|
254
|
+
token,
|
|
255
|
+
`/repos/${repo}/git/commits`,
|
|
256
|
+
{
|
|
257
|
+
method: "POST",
|
|
258
|
+
body: JSON.stringify({
|
|
259
|
+
message,
|
|
260
|
+
tree: newTree.sha,
|
|
261
|
+
parents: [parentSha]
|
|
262
|
+
})
|
|
263
|
+
}
|
|
264
|
+
);
|
|
265
|
+
|
|
266
|
+
await githubRequest(
|
|
267
|
+
token,
|
|
268
|
+
`/repos/${repo}/git/refs/heads/main`,
|
|
269
|
+
{
|
|
270
|
+
method: "PATCH",
|
|
271
|
+
body: JSON.stringify({
|
|
272
|
+
sha: commit.sha
|
|
273
|
+
})
|
|
274
|
+
}
|
|
275
|
+
);
|
|
276
|
+
|
|
277
|
+
const tagRef = `refs/tags/v${version}`;
|
|
278
|
+
|
|
279
|
+
await githubRequest(
|
|
280
|
+
token,
|
|
281
|
+
`/repos/${repo}/git/refs`,
|
|
282
|
+
{
|
|
283
|
+
method: "POST",
|
|
284
|
+
body: JSON.stringify({
|
|
285
|
+
ref: tagRef,
|
|
286
|
+
sha: commit.sha
|
|
287
|
+
})
|
|
288
|
+
}
|
|
289
|
+
);
|
|
290
|
+
|
|
291
|
+
return {
|
|
292
|
+
success: true,
|
|
293
|
+
commit: commit.sha,
|
|
294
|
+
tree: newTree.sha,
|
|
295
|
+
version,
|
|
296
|
+
tag: `v${version}`
|
|
297
|
+
};
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
export async function createGitHubRepo(token, repository, organization = false) {
|
|
301
|
+
const match = repository.match(
|
|
302
|
+
/^([a-zA-Z0-9-]+)\/([a-zA-Z0-9._-]+)$/
|
|
303
|
+
);
|
|
304
|
+
|
|
305
|
+
if (!match) {
|
|
306
|
+
return {
|
|
307
|
+
success: false,
|
|
308
|
+
msg: "Repository must have format user/repo"
|
|
309
|
+
};
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
const [, owner, repo] = match;
|
|
313
|
+
|
|
314
|
+
try {
|
|
315
|
+
const check = await fetch(
|
|
316
|
+
`${GITHUB_API}/repos/${owner}/${repo}`,
|
|
317
|
+
{
|
|
318
|
+
headers: githubHeaders(token)
|
|
319
|
+
}
|
|
320
|
+
);
|
|
321
|
+
|
|
322
|
+
if (check.ok) {
|
|
323
|
+
return {
|
|
324
|
+
success: false,
|
|
325
|
+
msg: `Repository ${repository} already exists`
|
|
326
|
+
};
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
if (check.status !== 404) {
|
|
330
|
+
const data = await check.json();
|
|
331
|
+
|
|
332
|
+
return {
|
|
333
|
+
success: false,
|
|
334
|
+
msg: data.message || `GitHub API error: ${check.status}`
|
|
335
|
+
};
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
// Organization repositories use the org endpoint; user repositories require ownership.
|
|
339
|
+
if (!organization) {
|
|
340
|
+
const userResponse = await fetch(
|
|
341
|
+
`${GITHUB_API}/user`,
|
|
342
|
+
{
|
|
343
|
+
headers: githubHeaders(token)
|
|
344
|
+
}
|
|
345
|
+
);
|
|
346
|
+
|
|
347
|
+
if (!userResponse.ok) {
|
|
348
|
+
const data = await userResponse.json();
|
|
349
|
+
|
|
350
|
+
return {
|
|
351
|
+
success: false,
|
|
352
|
+
msg: data.message || "Failed to get GitHub user"
|
|
353
|
+
};
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
const user = await userResponse.json();
|
|
357
|
+
|
|
358
|
+
if (user.login.toLowerCase() !== owner.toLowerCase()) {
|
|
359
|
+
return {
|
|
360
|
+
success: false,
|
|
361
|
+
msg: `You cannot create ${repository}. Your GitHub account is ${user.login}`
|
|
362
|
+
};
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
const createUrl = organization
|
|
367
|
+
? `${GITHUB_API}/orgs/${owner}/repos`
|
|
368
|
+
: `${GITHUB_API}/user/repos`;
|
|
369
|
+
|
|
370
|
+
const response = await fetch(
|
|
371
|
+
createUrl,
|
|
372
|
+
{
|
|
373
|
+
method: "POST",
|
|
374
|
+
headers: githubHeaders(token, {
|
|
375
|
+
"Content-Type": "application/json"
|
|
376
|
+
}),
|
|
377
|
+
body: JSON.stringify({
|
|
378
|
+
name: repo,
|
|
379
|
+
private: false,
|
|
380
|
+
auto_init: true
|
|
381
|
+
})
|
|
382
|
+
}
|
|
383
|
+
);
|
|
384
|
+
|
|
385
|
+
const data = await response.json();
|
|
386
|
+
|
|
387
|
+
if (!response.ok) {
|
|
388
|
+
return {
|
|
389
|
+
success: false,
|
|
390
|
+
msg: data.message || `GitHub API error: ${response.status}`
|
|
391
|
+
};
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
return {
|
|
395
|
+
success: true,
|
|
396
|
+
msg: `Repository ${repository} created`,
|
|
397
|
+
repo: data
|
|
398
|
+
};
|
|
399
|
+
}
|
|
400
|
+
catch (error) {
|
|
401
|
+
return {
|
|
402
|
+
success: false,
|
|
403
|
+
msg: error instanceof Error
|
|
404
|
+
? error.message
|
|
405
|
+
: String(error)
|
|
406
|
+
};
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
export async function deleteGitHubRepo(token, repository) {
|
|
411
|
+
const match = repository.match(
|
|
412
|
+
/^([a-zA-Z0-9-]+)\/([a-zA-Z0-9._-]+)$/
|
|
413
|
+
);
|
|
414
|
+
|
|
415
|
+
if (!match) {
|
|
416
|
+
return {
|
|
417
|
+
success: false,
|
|
418
|
+
msg: "Repository must have format user/repo"
|
|
419
|
+
};
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
const [, owner, repo] = match;
|
|
423
|
+
|
|
424
|
+
try {
|
|
425
|
+
const response = await fetch(
|
|
426
|
+
`${GITHUB_API}/repos/${owner}/${repo}`,
|
|
427
|
+
{
|
|
428
|
+
method: "DELETE",
|
|
429
|
+
headers: githubHeaders(token)
|
|
430
|
+
}
|
|
431
|
+
);
|
|
432
|
+
|
|
433
|
+
if (response.status === 204) {
|
|
434
|
+
return {
|
|
435
|
+
success: true,
|
|
436
|
+
msg: `Repository ${repository} deleted`
|
|
437
|
+
};
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
const data = await response.json().catch(() => null);
|
|
441
|
+
|
|
442
|
+
return {
|
|
443
|
+
success: false,
|
|
444
|
+
msg: data?.message || `GitHub API error: ${response.status}`
|
|
445
|
+
};
|
|
446
|
+
}
|
|
447
|
+
catch (error) {
|
|
448
|
+
return {
|
|
449
|
+
success: false,
|
|
450
|
+
msg: error instanceof Error
|
|
451
|
+
? error.message
|
|
452
|
+
: String(error)
|
|
453
|
+
};
|
|
454
|
+
}
|
|
455
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import fs from "node:fs"
|
|
2
|
+
import path from "node:path"
|
|
3
|
+
import { rootPath } from "../helpers.js"
|
|
4
|
+
|
|
5
|
+
export function lockFile(dir = rootPath) {
|
|
6
|
+
return path.join(dir, "spm.lock.json")
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export function readLock(dir = rootPath) {
|
|
10
|
+
try {
|
|
11
|
+
const data = JSON.parse(fs.readFileSync(lockFile(dir), "utf8"))
|
|
12
|
+
if (!data.packages || typeof data.packages !== "object") data.packages = {}
|
|
13
|
+
return data
|
|
14
|
+
} catch {
|
|
15
|
+
return { packages: {} }
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function writeLock(data, dir = rootPath) {
|
|
20
|
+
fs.writeFileSync(lockFile(dir), JSON.stringify(data, null, 4) + "\n", "utf8")
|
|
21
|
+
return data
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function setLockEntry(name, entry, dir = rootPath) {
|
|
25
|
+
const lock = readLock(dir)
|
|
26
|
+
lock.packages[name] = entry
|
|
27
|
+
return writeLock(lock, dir)
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function removeLockEntry(name, dir = rootPath) {
|
|
31
|
+
const lock = readLock(dir)
|
|
32
|
+
if (name in lock.packages) {
|
|
33
|
+
delete lock.packages[name]
|
|
34
|
+
writeLock(lock, dir)
|
|
35
|
+
}
|
|
36
|
+
return lock
|
|
37
|
+
}
|