create-shape-app 0.1.2 → 0.1.4
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 +4 -0
- package/dist/cli/help.js +1 -0
- package/dist/index.js +38 -3
- package/dist/template/release.js +80 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -29,10 +29,14 @@ Options:
|
|
|
29
29
|
- `--skip-install`
|
|
30
30
|
- `--skip-git`
|
|
31
31
|
- `--template-ref <tag>`
|
|
32
|
+
- `-h, --help`
|
|
33
|
+
- `-v, --version`
|
|
32
34
|
|
|
33
35
|
## Behavior
|
|
34
36
|
- Scaffolds from `shape-network/builder-kit` release tags only (`latest` by default).
|
|
35
37
|
- Rejects non-release refs (for example `main`) and canary tags.
|
|
38
|
+
- In non-interactive terminals, `--yes` is required.
|
|
39
|
+
- In interactive terminals, package manager is selectable when `--pm` is not provided.
|
|
36
40
|
- Copies template files, excluding VCS/internal maintainer metadata.
|
|
37
41
|
- Applies defaults:
|
|
38
42
|
- Root `package.json` name is set from the project directory name.
|
package/dist/cli/help.js
CHANGED
package/dist/index.js
CHANGED
|
@@ -12,8 +12,10 @@ import { fetchTemplateRelease } from './template/release.js';
|
|
|
12
12
|
const require = createRequire(import.meta.url);
|
|
13
13
|
const packageJson = require('../package.json');
|
|
14
14
|
export const CLI_VERSION = packageJson.version ?? '0.0.0';
|
|
15
|
+
const PACKAGE_MANAGERS = ['npm', 'pnpm', 'yarn', 'bun'];
|
|
16
|
+
const PACKAGE_MANAGER_SET = new Set(PACKAGE_MANAGERS);
|
|
15
17
|
const DEFAULT_PROJECT_NAME_PROMPT = 'Project name: ';
|
|
16
|
-
const DEFAULT_CONFIRM_PROMPT = 'Continue? (
|
|
18
|
+
const DEFAULT_CONFIRM_PROMPT = 'Continue? (Y/n): ';
|
|
17
19
|
export async function runCLI(argv, runtimeOverrides = {}) {
|
|
18
20
|
const runtime = createRuntime(runtimeOverrides);
|
|
19
21
|
try {
|
|
@@ -34,7 +36,7 @@ export async function runCLI(argv, runtimeOverrides = {}) {
|
|
|
34
36
|
return 1;
|
|
35
37
|
}
|
|
36
38
|
assertValidProjectName(projectName);
|
|
37
|
-
const packageManager = options.packageManager
|
|
39
|
+
const packageManager = await resolvePackageManager(options.packageManager, options.yes, runtime);
|
|
38
40
|
const targetDirectory = path.resolve(runtime.cwd, projectName);
|
|
39
41
|
if (!options.yes) {
|
|
40
42
|
if (!isInteractive(runtime)) {
|
|
@@ -131,6 +133,38 @@ async function resolveProjectName(projectName, runtime) {
|
|
|
131
133
|
const nextProjectName = answer.trim();
|
|
132
134
|
return nextProjectName || undefined;
|
|
133
135
|
}
|
|
136
|
+
async function resolvePackageManager(packageManager, skipPrompts, runtime) {
|
|
137
|
+
if (packageManager) {
|
|
138
|
+
return packageManager;
|
|
139
|
+
}
|
|
140
|
+
const detectedPackageManager = detectPackageManager(runtime.env.npm_config_user_agent);
|
|
141
|
+
if (!isInteractive(runtime) || skipPrompts) {
|
|
142
|
+
return detectedPackageManager;
|
|
143
|
+
}
|
|
144
|
+
runtime.print('Package manager:');
|
|
145
|
+
for (const [index, candidate] of PACKAGE_MANAGERS.entries()) {
|
|
146
|
+
const defaultLabel = candidate === detectedPackageManager ? ' (default)' : '';
|
|
147
|
+
runtime.print(` ${index + 1}) ${candidate}${defaultLabel}`);
|
|
148
|
+
}
|
|
149
|
+
while (true) {
|
|
150
|
+
const answer = (await runtime.prompt(`Select package manager (1-${PACKAGE_MANAGERS.length}) [${detectedPackageManager}]: `))
|
|
151
|
+
.trim()
|
|
152
|
+
.toLowerCase();
|
|
153
|
+
if (!answer) {
|
|
154
|
+
return detectedPackageManager;
|
|
155
|
+
}
|
|
156
|
+
const selectedByIndex = Number(answer);
|
|
157
|
+
if (Number.isInteger(selectedByIndex) &&
|
|
158
|
+
selectedByIndex >= 1 &&
|
|
159
|
+
selectedByIndex <= PACKAGE_MANAGERS.length) {
|
|
160
|
+
return PACKAGE_MANAGERS[selectedByIndex - 1];
|
|
161
|
+
}
|
|
162
|
+
if (PACKAGE_MANAGER_SET.has(answer)) {
|
|
163
|
+
return answer;
|
|
164
|
+
}
|
|
165
|
+
runtime.printError(`Invalid package manager: ${answer}. Enter 1-${PACKAGE_MANAGERS.length} or one of ${PACKAGE_MANAGERS.join(', ')}.`);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
134
168
|
function isInteractive(runtime) {
|
|
135
169
|
return runtime.stdinIsTTY && runtime.stdoutIsTTY;
|
|
136
170
|
}
|
|
@@ -148,7 +182,8 @@ async function defaultPrompt(message) {
|
|
|
148
182
|
}
|
|
149
183
|
async function defaultConfirm(message) {
|
|
150
184
|
const answer = await defaultPrompt(message);
|
|
151
|
-
|
|
185
|
+
const normalized = answer.trim().toLowerCase();
|
|
186
|
+
return normalized === '' || normalized === 'y' || normalized === 'yes';
|
|
152
187
|
}
|
|
153
188
|
function assertValidProjectName(projectName) {
|
|
154
189
|
if (projectName === '.' || projectName === '..') {
|
package/dist/template/release.js
CHANGED
|
@@ -23,10 +23,11 @@ export async function fetchTemplateRelease(options = {}) {
|
|
|
23
23
|
const endpoint = templateRef
|
|
24
24
|
? `https://api.github.com/repos/${owner}/${repo}/releases/tags/${encodeURIComponent(templateRef)}`
|
|
25
25
|
: `https://api.github.com/repos/${owner}/${repo}/releases/latest`;
|
|
26
|
+
const headers = buildHeaders(githubToken);
|
|
26
27
|
let response;
|
|
27
28
|
for (let attempt = 1; attempt <= maxAttempts; attempt += 1) {
|
|
28
29
|
response = await fetchImpl(endpoint, {
|
|
29
|
-
headers
|
|
30
|
+
headers,
|
|
30
31
|
});
|
|
31
32
|
if (response.ok) {
|
|
32
33
|
break;
|
|
@@ -40,6 +41,37 @@ export async function fetchTemplateRelease(options = {}) {
|
|
|
40
41
|
throw new Error('Failed to resolve template release: no response received.');
|
|
41
42
|
}
|
|
42
43
|
if (!response.ok) {
|
|
44
|
+
if (response.status === 404) {
|
|
45
|
+
if (templateRef) {
|
|
46
|
+
const resolvedTag = await resolveTemplateRefTag({
|
|
47
|
+
owner,
|
|
48
|
+
repo,
|
|
49
|
+
templateRef,
|
|
50
|
+
githubToken,
|
|
51
|
+
fetchImpl,
|
|
52
|
+
});
|
|
53
|
+
if (resolvedTag) {
|
|
54
|
+
return {
|
|
55
|
+
tag: resolvedTag,
|
|
56
|
+
tarballUrl: buildTagTarballUrl(owner, repo, resolvedTag),
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
else {
|
|
61
|
+
const latestTag = await resolveLatestSupportedTag({
|
|
62
|
+
owner,
|
|
63
|
+
repo,
|
|
64
|
+
githubToken,
|
|
65
|
+
fetchImpl,
|
|
66
|
+
});
|
|
67
|
+
if (latestTag) {
|
|
68
|
+
return {
|
|
69
|
+
tag: latestTag,
|
|
70
|
+
tarballUrl: buildTagTarballUrl(owner, repo, latestTag),
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
43
75
|
throw new Error(await buildReleaseLookupError(response, templateRef));
|
|
44
76
|
}
|
|
45
77
|
const payload = (await response.json());
|
|
@@ -54,6 +86,9 @@ export async function fetchTemplateRelease(options = {}) {
|
|
|
54
86
|
tarballUrl,
|
|
55
87
|
};
|
|
56
88
|
}
|
|
89
|
+
function buildTagTarballUrl(owner, repo, tag) {
|
|
90
|
+
return `https://api.github.com/repos/${owner}/${repo}/tarball/${encodeURIComponent(tag)}`;
|
|
91
|
+
}
|
|
57
92
|
function buildHeaders(githubToken) {
|
|
58
93
|
const headers = {
|
|
59
94
|
Accept: 'application/vnd.github+json',
|
|
@@ -84,6 +119,50 @@ async function buildReleaseLookupError(response, templateRef) {
|
|
|
84
119
|
}
|
|
85
120
|
return `Failed to resolve ${refLabel}: HTTP ${response.status}.`;
|
|
86
121
|
}
|
|
122
|
+
async function resolveLatestSupportedTag(options) {
|
|
123
|
+
const tags = await fetchTagNames(options);
|
|
124
|
+
return tags.find((tag) => isSupportedTag(tag));
|
|
125
|
+
}
|
|
126
|
+
async function resolveTemplateRefTag(options) {
|
|
127
|
+
const tags = await fetchTagNames(options);
|
|
128
|
+
const exactMatch = tags.find((tag) => tag === options.templateRef && isSupportedTag(tag));
|
|
129
|
+
if (exactMatch) {
|
|
130
|
+
return exactMatch;
|
|
131
|
+
}
|
|
132
|
+
const normalizedTemplateRef = normalizeTag(options.templateRef);
|
|
133
|
+
return tags.find((tag) => normalizeTag(tag) === normalizedTemplateRef && isSupportedTag(tag));
|
|
134
|
+
}
|
|
135
|
+
async function fetchTagNames(options) {
|
|
136
|
+
const response = await options.fetchImpl(`https://api.github.com/repos/${options.owner}/${options.repo}/tags?per_page=100`, {
|
|
137
|
+
headers: buildHeaders(options.githubToken),
|
|
138
|
+
});
|
|
139
|
+
if (!response.ok) {
|
|
140
|
+
return [];
|
|
141
|
+
}
|
|
142
|
+
const payload = (await response.json());
|
|
143
|
+
if (!Array.isArray(payload)) {
|
|
144
|
+
return [];
|
|
145
|
+
}
|
|
146
|
+
return payload.flatMap((item) => {
|
|
147
|
+
if (!item || typeof item !== 'object') {
|
|
148
|
+
return [];
|
|
149
|
+
}
|
|
150
|
+
const name = item.name;
|
|
151
|
+
return typeof name === 'string' && name.trim() ? [name] : [];
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
function isSupportedTag(tag) {
|
|
155
|
+
try {
|
|
156
|
+
assertTagIsSupported(tag);
|
|
157
|
+
return true;
|
|
158
|
+
}
|
|
159
|
+
catch {
|
|
160
|
+
return false;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
function normalizeTag(tag) {
|
|
164
|
+
return tag.startsWith('v') ? tag.slice(1) : tag;
|
|
165
|
+
}
|
|
87
166
|
async function readApiMessage(response) {
|
|
88
167
|
try {
|
|
89
168
|
const payload = (await response.clone().json());
|