ecopages 0.2.0-rc.7 → 0.2.0-rc.8
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/bin/git-template-source-providers.js +56 -0
- package/bin/git-template-source-shared.js +7 -0
- package/bin/git-template-source.js +22 -48
- package/bin/init.js +107 -56
- package/package.json +2 -2
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { repositoryName, withSubpath } from './git-template-source-shared.js';
|
|
2
|
+
|
|
3
|
+
export function normalizeGithubParts(parts, source) {
|
|
4
|
+
if (parts.length < 2) {
|
|
5
|
+
return null;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
const [owner, repositoryPart, marker, ref, ...subpath] = parts;
|
|
9
|
+
const repository = `${owner}/${repositoryName(repositoryPart)}`;
|
|
10
|
+
if (marker === 'tree' || marker === 'blob') {
|
|
11
|
+
if (!ref) throw new Error(`Template URL '${source}' is missing a Git ref.`);
|
|
12
|
+
return `github:${withSubpath(repository, subpath)}#${ref}`;
|
|
13
|
+
}
|
|
14
|
+
return `github:${repository}`;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function normalizeGitlabParts(parts) {
|
|
18
|
+
const markerIndex = parts.indexOf('-');
|
|
19
|
+
const treeIndex = parts.findIndex((part) => part === 'tree' || part === 'blob');
|
|
20
|
+
if (markerIndex > 0 && treeIndex > markerIndex && parts[treeIndex + 1]) {
|
|
21
|
+
const repository = parts.slice(0, markerIndex).map(repositoryName).join('/');
|
|
22
|
+
const ref = parts[treeIndex + 1];
|
|
23
|
+
const subpath = parts.slice(treeIndex + 2);
|
|
24
|
+
return `gitlab:${withSubpath(repository, subpath)}#${ref}`;
|
|
25
|
+
}
|
|
26
|
+
if (parts.length >= 2) return `gitlab:${parts.join('/')}`;
|
|
27
|
+
return null;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function normalizeBitbucketParts(parts) {
|
|
31
|
+
if (parts.length < 2) {
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const [workspace, repositoryPart, marker, ref, ...subpath] = parts;
|
|
36
|
+
const repository = `${workspace}/${repositoryName(repositoryPart)}`;
|
|
37
|
+
if (marker === 'src' && ref) {
|
|
38
|
+
return `bitbucket:${withSubpath(repository, subpath)}#${ref}`;
|
|
39
|
+
}
|
|
40
|
+
return `bitbucket:${repository}`;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function normalizeSourcehutParts(parts) {
|
|
44
|
+
if (parts.length < 2) {
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const treeIndex = parts.findIndex((part) => part === 'tree' || part === 'blob');
|
|
49
|
+
if (treeIndex > 1 && parts[treeIndex + 1]) {
|
|
50
|
+
const repository = parts.slice(0, treeIndex).map(repositoryName).join('/');
|
|
51
|
+
const ref = parts[treeIndex + 1];
|
|
52
|
+
const subpath = parts.slice(treeIndex + 2);
|
|
53
|
+
return `sourcehut:${withSubpath(repository, subpath)}#${ref}`;
|
|
54
|
+
}
|
|
55
|
+
return `sourcehut:${parts.join('/')}`;
|
|
56
|
+
}
|
|
@@ -1,3 +1,10 @@
|
|
|
1
|
+
import {
|
|
2
|
+
normalizeBitbucketParts,
|
|
3
|
+
normalizeGithubParts,
|
|
4
|
+
normalizeGitlabParts,
|
|
5
|
+
normalizeSourcehutParts,
|
|
6
|
+
} from './git-template-source-providers.js';
|
|
7
|
+
|
|
1
8
|
const GIT_PROVIDER_PREFIX = /^(github|gitlab|bitbucket|sourcehut):/u;
|
|
2
9
|
|
|
3
10
|
const HOST_PROVIDER = {
|
|
@@ -7,12 +14,21 @@ const HOST_PROVIDER = {
|
|
|
7
14
|
'git.sr.ht': 'sourcehut',
|
|
8
15
|
};
|
|
9
16
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
17
|
+
const PROVIDER_NORMALIZERS = {
|
|
18
|
+
github: normalizeGithubParts,
|
|
19
|
+
gitlab: normalizeGitlabParts,
|
|
20
|
+
bitbucket: normalizeBitbucketParts,
|
|
21
|
+
sourcehut: normalizeSourcehutParts,
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
function normalizeProviderUrl(hostProvider, parts, source) {
|
|
25
|
+
const normalize = PROVIDER_NORMALIZERS[hostProvider];
|
|
26
|
+
const normalized = normalize?.(parts, source);
|
|
27
|
+
if (normalized) {
|
|
28
|
+
return normalized;
|
|
29
|
+
}
|
|
13
30
|
|
|
14
|
-
|
|
15
|
-
return subpath.length > 0 ? `${repository}/${subpath.join('/')}` : repository;
|
|
31
|
+
throw new Error(`Unsupported template URL '${source}'. Include a repository and optional Git ref.`);
|
|
16
32
|
}
|
|
17
33
|
|
|
18
34
|
/**
|
|
@@ -42,47 +58,5 @@ export function normalizeGitSource(source) {
|
|
|
42
58
|
.split('/')
|
|
43
59
|
.filter(Boolean);
|
|
44
60
|
|
|
45
|
-
|
|
46
|
-
const [owner, repositoryPart, marker, ref, ...subpath] = parts;
|
|
47
|
-
const repository = `${owner}/${repositoryName(repositoryPart)}`;
|
|
48
|
-
if (marker === 'tree' || marker === 'blob') {
|
|
49
|
-
if (!ref) throw new Error(`Template URL '${source}' is missing a Git ref.`);
|
|
50
|
-
return `github:${withSubpath(repository, subpath)}#${ref}`;
|
|
51
|
-
}
|
|
52
|
-
return `github:${repository}`;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
if (hostProvider === 'gitlab') {
|
|
56
|
-
const markerIndex = parts.indexOf('-');
|
|
57
|
-
const treeIndex = parts.findIndex((part) => part === 'tree' || part === 'blob');
|
|
58
|
-
if (markerIndex > 0 && treeIndex > markerIndex && parts[treeIndex + 1]) {
|
|
59
|
-
const repository = parts.slice(0, markerIndex).map(repositoryName).join('/');
|
|
60
|
-
const ref = parts[treeIndex + 1];
|
|
61
|
-
const subpath = parts.slice(treeIndex + 2);
|
|
62
|
-
return `gitlab:${withSubpath(repository, subpath)}#${ref}`;
|
|
63
|
-
}
|
|
64
|
-
if (parts.length >= 2) return `gitlab:${parts.join('/')}`;
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
if (hostProvider === 'bitbucket' && parts.length >= 2) {
|
|
68
|
-
const [workspace, repositoryPart, marker, ref, ...subpath] = parts;
|
|
69
|
-
const repository = `${workspace}/${repositoryName(repositoryPart)}`;
|
|
70
|
-
if (marker === 'src' && ref) {
|
|
71
|
-
return `bitbucket:${withSubpath(repository, subpath)}#${ref}`;
|
|
72
|
-
}
|
|
73
|
-
return `bitbucket:${repository}`;
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
if (hostProvider === 'sourcehut' && parts.length >= 2) {
|
|
77
|
-
const treeIndex = parts.findIndex((part) => part === 'tree' || part === 'blob');
|
|
78
|
-
if (treeIndex > 1 && parts[treeIndex + 1]) {
|
|
79
|
-
const repository = parts.slice(0, treeIndex).map(repositoryName).join('/');
|
|
80
|
-
const ref = parts[treeIndex + 1];
|
|
81
|
-
const subpath = parts.slice(treeIndex + 2);
|
|
82
|
-
return `sourcehut:${withSubpath(repository, subpath)}#${ref}`;
|
|
83
|
-
}
|
|
84
|
-
return `sourcehut:${parts.join('/')}`;
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
throw new Error(`Unsupported template URL '${source}'. Include a repository and optional Git ref.`);
|
|
61
|
+
return normalizeProviderUrl(hostProvider, parts, source);
|
|
88
62
|
}
|
package/bin/init.js
CHANGED
|
@@ -220,52 +220,114 @@ function isCancelled(value) {
|
|
|
220
220
|
return false;
|
|
221
221
|
}
|
|
222
222
|
|
|
223
|
+
async function promptProjectDirectory() {
|
|
224
|
+
const answer = await prompts.text({
|
|
225
|
+
message: 'Project directory:',
|
|
226
|
+
placeholder: 'my-app',
|
|
227
|
+
validate: (value) => (value.trim().length === 0 ? 'Enter a project directory.' : undefined),
|
|
228
|
+
});
|
|
229
|
+
if (isCancelled(answer)) return null;
|
|
230
|
+
return answer.trim();
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
async function promptTemplateSelection() {
|
|
234
|
+
const answer = await prompts.select({
|
|
235
|
+
message: 'Select a template:',
|
|
236
|
+
options: [
|
|
237
|
+
...officialTemplates.map((template) => ({
|
|
238
|
+
label: template.displayName,
|
|
239
|
+
value: template.id,
|
|
240
|
+
hint: template.description,
|
|
241
|
+
})),
|
|
242
|
+
{
|
|
243
|
+
label: 'Community template',
|
|
244
|
+
value: '__community__',
|
|
245
|
+
hint: 'Use a Git repository or provider source.',
|
|
246
|
+
},
|
|
247
|
+
],
|
|
248
|
+
});
|
|
249
|
+
if (isCancelled(answer)) return null;
|
|
250
|
+
return answer;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
async function promptCommunityTemplateSource() {
|
|
254
|
+
const sourceAnswer = await prompts.text({
|
|
255
|
+
message: 'Template source:',
|
|
256
|
+
placeholder: 'github:owner/repository#v1.0.0',
|
|
257
|
+
validate: (value) => (value.trim().length === 0 ? 'Enter a Git template source.' : undefined),
|
|
258
|
+
});
|
|
259
|
+
if (isCancelled(sourceAnswer)) return null;
|
|
260
|
+
return sourceAnswer.trim();
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
async function resolveInteractiveTemplateChoice(templateId, source) {
|
|
264
|
+
if (templateId || source) {
|
|
265
|
+
return { templateId, source };
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
const answer = await promptTemplateSelection();
|
|
269
|
+
if (answer === null) {
|
|
270
|
+
return null;
|
|
271
|
+
}
|
|
272
|
+
if (answer === '__community__') {
|
|
273
|
+
const communitySource = await promptCommunityTemplateSource();
|
|
274
|
+
if (communitySource === null) {
|
|
275
|
+
return null;
|
|
276
|
+
}
|
|
277
|
+
return { templateId, source: communitySource };
|
|
278
|
+
}
|
|
279
|
+
return { templateId: answer, source };
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
async function promptInstallChoice(packageManager, parsed) {
|
|
283
|
+
if (parsed.start) {
|
|
284
|
+
return true;
|
|
285
|
+
}
|
|
286
|
+
if (parsed.install !== undefined) {
|
|
287
|
+
return parsed.install;
|
|
288
|
+
}
|
|
289
|
+
if (!parsed.interactive) {
|
|
290
|
+
return undefined;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
const answer = await prompts.confirm({
|
|
294
|
+
message: `Install dependencies with ${packageManager}?`,
|
|
295
|
+
initialValue: true,
|
|
296
|
+
});
|
|
297
|
+
if (isCancelled(answer)) return null;
|
|
298
|
+
return answer;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
async function promptStartChoice(parsed, install) {
|
|
302
|
+
if (parsed.start !== undefined) {
|
|
303
|
+
return parsed.start;
|
|
304
|
+
}
|
|
305
|
+
if (!parsed.interactive || !install) {
|
|
306
|
+
return undefined;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
const answer = await prompts.confirm({ message: 'Start the development server now?', initialValue: false });
|
|
310
|
+
if (isCancelled(answer)) return null;
|
|
311
|
+
return answer;
|
|
312
|
+
}
|
|
313
|
+
|
|
223
314
|
async function collectInitAnswers(parsed) {
|
|
224
315
|
const packageManager = detectPackageManager();
|
|
225
316
|
let dir = parsed.dir;
|
|
226
317
|
let templateId = parsed.template;
|
|
227
318
|
let source = parsed.from;
|
|
228
319
|
|
|
229
|
-
if (parsed.interactive) {
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
validate: (value) => (value.trim().length === 0 ? 'Enter a project directory.' : undefined),
|
|
235
|
-
});
|
|
236
|
-
if (isCancelled(answer)) return null;
|
|
237
|
-
dir = answer.trim();
|
|
238
|
-
}
|
|
320
|
+
if (parsed.interactive && !dir) {
|
|
321
|
+
const promptedDir = await promptProjectDirectory();
|
|
322
|
+
if (promptedDir === null) return null;
|
|
323
|
+
dir = promptedDir;
|
|
324
|
+
}
|
|
239
325
|
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
label: template.displayName,
|
|
246
|
-
value: template.id,
|
|
247
|
-
hint: template.description,
|
|
248
|
-
})),
|
|
249
|
-
{
|
|
250
|
-
label: 'Community template',
|
|
251
|
-
value: '__community__',
|
|
252
|
-
hint: 'Use a Git repository or provider source.',
|
|
253
|
-
},
|
|
254
|
-
],
|
|
255
|
-
});
|
|
256
|
-
if (isCancelled(answer)) return null;
|
|
257
|
-
if (answer === '__community__') {
|
|
258
|
-
const sourceAnswer = await prompts.text({
|
|
259
|
-
message: 'Template source:',
|
|
260
|
-
placeholder: 'github:owner/repository#v1.0.0',
|
|
261
|
-
validate: (value) => (value.trim().length === 0 ? 'Enter a Git template source.' : undefined),
|
|
262
|
-
});
|
|
263
|
-
if (isCancelled(sourceAnswer)) return null;
|
|
264
|
-
source = sourceAnswer.trim();
|
|
265
|
-
} else {
|
|
266
|
-
templateId = answer;
|
|
267
|
-
}
|
|
268
|
-
}
|
|
326
|
+
if (parsed.interactive) {
|
|
327
|
+
const templateChoice = await resolveInteractiveTemplateChoice(templateId, source);
|
|
328
|
+
if (templateChoice === null) return null;
|
|
329
|
+
templateId = templateChoice.templateId;
|
|
330
|
+
source = templateChoice.source;
|
|
269
331
|
}
|
|
270
332
|
|
|
271
333
|
if (!templateId && !source) templateId = defaultOfficialTemplate.id;
|
|
@@ -273,24 +335,13 @@ async function collectInitAnswers(parsed) {
|
|
|
273
335
|
prompts.log.step(`Resolved template source: ${getTemplateSource(templateId, source)}`);
|
|
274
336
|
}
|
|
275
337
|
|
|
276
|
-
|
|
277
|
-
if (
|
|
278
|
-
|
|
279
|
-
message: `Install dependencies with ${packageManager}?`,
|
|
280
|
-
initialValue: true,
|
|
281
|
-
});
|
|
282
|
-
if (isCancelled(answer)) return null;
|
|
283
|
-
install = answer;
|
|
284
|
-
}
|
|
285
|
-
install ??= false;
|
|
338
|
+
const installAnswer = await promptInstallChoice(packageManager, parsed);
|
|
339
|
+
if (installAnswer === null) return null;
|
|
340
|
+
const install = installAnswer ?? false;
|
|
286
341
|
|
|
287
|
-
|
|
288
|
-
if (
|
|
289
|
-
|
|
290
|
-
if (isCancelled(answer)) return null;
|
|
291
|
-
start = answer;
|
|
292
|
-
}
|
|
293
|
-
start ??= false;
|
|
342
|
+
const startAnswer = await promptStartChoice(parsed, install);
|
|
343
|
+
if (startAnswer === null) return null;
|
|
344
|
+
const start = startAnswer ?? false;
|
|
294
345
|
|
|
295
346
|
return { dir, templateId, source, packageManager, install, start };
|
|
296
347
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ecopages",
|
|
3
|
-
"version": "0.2.0-rc.
|
|
3
|
+
"version": "0.2.0-rc.8",
|
|
4
4
|
"description": "CLI utilities for Ecopages",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"engines": {
|
|
@@ -36,7 +36,7 @@
|
|
|
36
36
|
},
|
|
37
37
|
"dependencies": {
|
|
38
38
|
"@clack/prompts": "^1.7.0",
|
|
39
|
-
"@ecopages/core": "0.2.0-rc.
|
|
39
|
+
"@ecopages/core": "0.2.0-rc.8",
|
|
40
40
|
"@ecopages/logger": "^0.2.3",
|
|
41
41
|
"giget": "^2.0.0",
|
|
42
42
|
"tsx": "^4.22.3"
|