create-shape-app 0.1.4 → 0.1.6
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/dist/index.d.ts +7 -0
- package/dist/index.js +98 -26
- package/dist/template/materialize.js +0 -1
- package/dist/template/release.js +26 -0
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type PackageManager } from './cli/args.js';
|
|
1
2
|
import { type PostScaffoldSetupOptions, type PostScaffoldSetupResult } from './scaffold/post-setup.js';
|
|
2
3
|
import { type MaterializedTemplate } from './template/materialize.js';
|
|
3
4
|
import { type TemplateRelease } from './template/release.js';
|
|
@@ -10,6 +11,7 @@ interface CliRuntime {
|
|
|
10
11
|
print: (message: string) => void;
|
|
11
12
|
printError: (message: string) => void;
|
|
12
13
|
prompt: (message: string) => Promise<string>;
|
|
14
|
+
selectPackageManager: (options: PackageManagerSelectOptions) => Promise<PackageManager | undefined>;
|
|
13
15
|
confirm: (message: string) => Promise<boolean>;
|
|
14
16
|
resolveTemplateRelease: (templateRef?: string) => Promise<TemplateRelease>;
|
|
15
17
|
materializeTemplate: (release: TemplateRelease) => Promise<MaterializedTemplate>;
|
|
@@ -17,5 +19,10 @@ interface CliRuntime {
|
|
|
17
19
|
copyTemplateToDirectory: (templateRoot: string, targetDirectory: string) => Promise<void>;
|
|
18
20
|
runPostScaffoldSetup: (options: PostScaffoldSetupOptions) => Promise<PostScaffoldSetupResult>;
|
|
19
21
|
}
|
|
22
|
+
interface PackageManagerSelectOptions {
|
|
23
|
+
message: string;
|
|
24
|
+
choices: readonly PackageManager[];
|
|
25
|
+
defaultValue: PackageManager;
|
|
26
|
+
}
|
|
20
27
|
export declare function runCLI(argv: string[], runtimeOverrides?: Partial<CliRuntime>): Promise<number>;
|
|
21
28
|
export {};
|
package/dist/index.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import path from 'node:path';
|
|
2
2
|
import { pathToFileURL } from 'node:url';
|
|
3
3
|
import { createRequire } from 'node:module';
|
|
4
|
-
import
|
|
4
|
+
import { createInterface } from 'node:readline/promises';
|
|
5
|
+
import { clearScreenDown, cursorTo, emitKeypressEvents, moveCursor } from 'node:readline';
|
|
5
6
|
import { parseArgs } from './cli/args.js';
|
|
6
7
|
import { CliUsageError } from './cli/errors.js';
|
|
7
8
|
import { HELP_TEXT } from './cli/help.js';
|
|
@@ -13,9 +14,9 @@ const require = createRequire(import.meta.url);
|
|
|
13
14
|
const packageJson = require('../package.json');
|
|
14
15
|
export const CLI_VERSION = packageJson.version ?? '0.0.0';
|
|
15
16
|
const PACKAGE_MANAGERS = ['npm', 'pnpm', 'yarn', 'bun'];
|
|
16
|
-
const PACKAGE_MANAGER_SET = new Set(PACKAGE_MANAGERS);
|
|
17
17
|
const DEFAULT_PROJECT_NAME_PROMPT = 'Project name: ';
|
|
18
18
|
const DEFAULT_CONFIRM_PROMPT = 'Continue? (Y/n): ';
|
|
19
|
+
const PACKAGE_MANAGER_SELECT_MESSAGE = 'Package manager:';
|
|
19
20
|
export async function runCLI(argv, runtimeOverrides = {}) {
|
|
20
21
|
const runtime = createRuntime(runtimeOverrides);
|
|
21
22
|
try {
|
|
@@ -110,6 +111,7 @@ function createRuntime(overrides) {
|
|
|
110
111
|
print: console.log,
|
|
111
112
|
printError: console.error,
|
|
112
113
|
prompt: defaultPrompt,
|
|
114
|
+
selectPackageManager: defaultSelectPackageManager,
|
|
113
115
|
confirm: defaultConfirm,
|
|
114
116
|
resolveTemplateRelease: (templateRef) => fetchTemplateRelease({
|
|
115
117
|
templateRef,
|
|
@@ -141,35 +143,17 @@ async function resolvePackageManager(packageManager, skipPrompts, runtime) {
|
|
|
141
143
|
if (!isInteractive(runtime) || skipPrompts) {
|
|
142
144
|
return detectedPackageManager;
|
|
143
145
|
}
|
|
144
|
-
runtime.
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
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
|
-
}
|
|
146
|
+
return ((await runtime.selectPackageManager({
|
|
147
|
+
message: PACKAGE_MANAGER_SELECT_MESSAGE,
|
|
148
|
+
choices: PACKAGE_MANAGERS,
|
|
149
|
+
defaultValue: detectedPackageManager,
|
|
150
|
+
})) ?? detectedPackageManager);
|
|
167
151
|
}
|
|
168
152
|
function isInteractive(runtime) {
|
|
169
153
|
return runtime.stdinIsTTY && runtime.stdoutIsTTY;
|
|
170
154
|
}
|
|
171
155
|
async function defaultPrompt(message) {
|
|
172
|
-
const rl =
|
|
156
|
+
const rl = createInterface({
|
|
173
157
|
input: process.stdin,
|
|
174
158
|
output: process.stdout,
|
|
175
159
|
});
|
|
@@ -185,6 +169,94 @@ async function defaultConfirm(message) {
|
|
|
185
169
|
const normalized = answer.trim().toLowerCase();
|
|
186
170
|
return normalized === '' || normalized === 'y' || normalized === 'yes';
|
|
187
171
|
}
|
|
172
|
+
async function defaultSelectPackageManager(options) {
|
|
173
|
+
const { message, choices, defaultValue } = options;
|
|
174
|
+
if (choices.length === 0) {
|
|
175
|
+
return undefined;
|
|
176
|
+
}
|
|
177
|
+
const defaultIndex = choices.indexOf(defaultValue);
|
|
178
|
+
let selectedIndex = defaultIndex >= 0 ? defaultIndex : 0;
|
|
179
|
+
if (!process.stdin.isTTY || !process.stdout.isTTY) {
|
|
180
|
+
return choices[selectedIndex];
|
|
181
|
+
}
|
|
182
|
+
const stdin = process.stdin;
|
|
183
|
+
const stdout = process.stdout;
|
|
184
|
+
const previousRawMode = Boolean(stdin.isRaw);
|
|
185
|
+
let renderedLines = 0;
|
|
186
|
+
const render = () => {
|
|
187
|
+
const lines = [
|
|
188
|
+
message,
|
|
189
|
+
...choices.map((candidate, index) => {
|
|
190
|
+
const indicator = index === selectedIndex ? '>' : ' ';
|
|
191
|
+
const defaultLabel = candidate === defaultValue ? ' (default)' : '';
|
|
192
|
+
return ` ${indicator} ${candidate}${defaultLabel}`;
|
|
193
|
+
}),
|
|
194
|
+
' Use Up/Down arrows and Enter to confirm.',
|
|
195
|
+
];
|
|
196
|
+
if (renderedLines > 0) {
|
|
197
|
+
moveCursor(stdout, 0, -renderedLines);
|
|
198
|
+
cursorTo(stdout, 0);
|
|
199
|
+
clearScreenDown(stdout);
|
|
200
|
+
}
|
|
201
|
+
stdout.write(lines.join('\n'));
|
|
202
|
+
stdout.write('\n');
|
|
203
|
+
renderedLines = lines.length;
|
|
204
|
+
};
|
|
205
|
+
emitKeypressEvents(stdin);
|
|
206
|
+
if (typeof stdin.setRawMode === 'function') {
|
|
207
|
+
stdin.setRawMode(true);
|
|
208
|
+
}
|
|
209
|
+
stdin.resume();
|
|
210
|
+
stdout.write('\x1b[?25l');
|
|
211
|
+
render();
|
|
212
|
+
try {
|
|
213
|
+
const selected = await new Promise((resolve, reject) => {
|
|
214
|
+
const onKeypress = (_value, key) => {
|
|
215
|
+
if (!key) {
|
|
216
|
+
return;
|
|
217
|
+
}
|
|
218
|
+
if (key.ctrl && key.name === 'c') {
|
|
219
|
+
stdin.off('keypress', onKeypress);
|
|
220
|
+
reject(new Error('Aborted.'));
|
|
221
|
+
return;
|
|
222
|
+
}
|
|
223
|
+
if (key.name === 'up' || key.name === 'k') {
|
|
224
|
+
selectedIndex = (selectedIndex - 1 + choices.length) % choices.length;
|
|
225
|
+
render();
|
|
226
|
+
return;
|
|
227
|
+
}
|
|
228
|
+
if (key.name === 'down' || key.name === 'j') {
|
|
229
|
+
selectedIndex = (selectedIndex + 1) % choices.length;
|
|
230
|
+
render();
|
|
231
|
+
return;
|
|
232
|
+
}
|
|
233
|
+
if (key.name === 'return' || key.name === 'enter') {
|
|
234
|
+
stdin.off('keypress', onKeypress);
|
|
235
|
+
resolve(choices[selectedIndex]);
|
|
236
|
+
return;
|
|
237
|
+
}
|
|
238
|
+
if (key.name === 'escape') {
|
|
239
|
+
stdin.off('keypress', onKeypress);
|
|
240
|
+
resolve(defaultValue);
|
|
241
|
+
}
|
|
242
|
+
};
|
|
243
|
+
stdin.on('keypress', onKeypress);
|
|
244
|
+
});
|
|
245
|
+
if (renderedLines > 0) {
|
|
246
|
+
moveCursor(stdout, 0, -renderedLines);
|
|
247
|
+
cursorTo(stdout, 0);
|
|
248
|
+
clearScreenDown(stdout);
|
|
249
|
+
}
|
|
250
|
+
stdout.write(`Package manager: ${selected}\n`);
|
|
251
|
+
return selected;
|
|
252
|
+
}
|
|
253
|
+
finally {
|
|
254
|
+
stdout.write('\x1b[?25h');
|
|
255
|
+
if (typeof stdin.setRawMode === 'function') {
|
|
256
|
+
stdin.setRawMode(previousRawMode);
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
}
|
|
188
260
|
function assertValidProjectName(projectName) {
|
|
189
261
|
if (projectName === '.' || projectName === '..') {
|
|
190
262
|
throw new CliUsageError('Invalid project name: "." and ".." are not allowed.');
|
|
@@ -26,7 +26,6 @@ export async function materializeTemplateFromRelease(release, fetchImpl = fetch)
|
|
|
26
26
|
async function downloadTarball(url, outputPath, fetchImpl) {
|
|
27
27
|
const response = await fetchImpl(url, {
|
|
28
28
|
headers: {
|
|
29
|
-
Accept: 'application/octet-stream',
|
|
30
29
|
'User-Agent': 'create-shape-app',
|
|
31
30
|
},
|
|
32
31
|
});
|
package/dist/template/release.js
CHANGED
|
@@ -70,6 +70,18 @@ export async function fetchTemplateRelease(options = {}) {
|
|
|
70
70
|
tarballUrl: buildTagTarballUrl(owner, repo, latestTag),
|
|
71
71
|
};
|
|
72
72
|
}
|
|
73
|
+
const defaultBranch = await resolveDefaultBranch({
|
|
74
|
+
owner,
|
|
75
|
+
repo,
|
|
76
|
+
githubToken,
|
|
77
|
+
fetchImpl,
|
|
78
|
+
});
|
|
79
|
+
if (defaultBranch) {
|
|
80
|
+
return {
|
|
81
|
+
tag: defaultBranch,
|
|
82
|
+
tarballUrl: buildTagTarballUrl(owner, repo, defaultBranch),
|
|
83
|
+
};
|
|
84
|
+
}
|
|
73
85
|
}
|
|
74
86
|
}
|
|
75
87
|
throw new Error(await buildReleaseLookupError(response, templateRef));
|
|
@@ -151,6 +163,20 @@ async function fetchTagNames(options) {
|
|
|
151
163
|
return typeof name === 'string' && name.trim() ? [name] : [];
|
|
152
164
|
});
|
|
153
165
|
}
|
|
166
|
+
async function resolveDefaultBranch(options) {
|
|
167
|
+
const response = await options.fetchImpl(`https://api.github.com/repos/${options.owner}/${options.repo}`, {
|
|
168
|
+
headers: buildHeaders(options.githubToken),
|
|
169
|
+
});
|
|
170
|
+
if (!response.ok) {
|
|
171
|
+
return undefined;
|
|
172
|
+
}
|
|
173
|
+
const payload = (await response.json());
|
|
174
|
+
const defaultBranch = payload.default_branch;
|
|
175
|
+
if (typeof defaultBranch !== 'string' || !defaultBranch.trim()) {
|
|
176
|
+
return undefined;
|
|
177
|
+
}
|
|
178
|
+
return defaultBranch;
|
|
179
|
+
}
|
|
154
180
|
function isSupportedTag(tag) {
|
|
155
181
|
try {
|
|
156
182
|
assertTagIsSupported(tag);
|