create-harper 0.0.1 → 0.0.3
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 +5 -0
- package/index.js +37 -214
- package/lib/constants/defaultTargetDir.js +1 -0
- package/lib/constants/frameworks.js +75 -0
- package/lib/constants/helpMessage.js +23 -0
- package/lib/constants/renameFiles.js +7 -0
- package/lib/constants/templates.js +6 -0
- package/lib/fs/applyAndWriteTemplateFile.js +9 -0
- package/lib/fs/copy.js +11 -0
- package/lib/fs/copyDir.js +12 -0
- package/lib/fs/crawlTemplateDir.js +18 -0
- package/lib/fs/emptyDir.js +14 -0
- package/lib/fs/formatTargetDir.js +3 -0
- package/lib/fs/isEmpty.js +6 -0
- package/lib/install.js +17 -0
- package/lib/pkg/getInstallCommand.js +6 -0
- package/lib/pkg/getRunCommand.js +12 -0
- package/lib/pkg/isValidPackageName.js +5 -0
- package/lib/pkg/pkgFromUserAgent.js +9 -0
- package/lib/pkg/toValidPackageName.js +9 -0
- package/lib/run.js +14 -0
- package/lib/start.js +15 -0
- package/lib/steps/getEnvVars.js +79 -0
- package/lib/steps/getImmediate.js +29 -0
- package/lib/steps/getPackageName.js +38 -0
- package/lib/steps/getProjectName.js +40 -0
- package/lib/steps/getTemplate.js +65 -0
- package/lib/steps/handleExistingDir.js +61 -0
- package/lib/steps/scaffoldProject.js +40 -0
- package/lib/steps/showOutro.js +30 -0
- package/package.json +20 -8
- package/template-barebones/README.md +7 -0
- package/template-barebones/_aiignore +1 -0
- package/template-barebones/_env +3 -0
- package/template-barebones/_env.example +3 -0
- package/template-barebones/_gitignore +147 -0
- package/template-barebones/config.yaml +7 -0
- package/template-barebones/graphql.config.yml +3 -0
- package/template-barebones/package.json +14 -0
- package/template-barebones/schema.graphql +1 -0
- package/template-react/README.md +45 -0
- package/template-react/_aiignore +1 -0
- package/template-react/_env +3 -0
- package/template-react/_env.example +3 -0
- package/template-react/_github/workflow/deploy.yaml +40 -0
- package/template-react/_gitignore +147 -0
- package/template-react/config.yaml +23 -0
- package/template-react/deploy-template/config.yaml +2 -0
- package/template-react/deploy-template/fastify/static.js +14 -0
- package/template-react/deploy-template/package.json +5 -0
- package/template-react/graphql.config.yml +3 -0
- package/template-react/index.html +13 -0
- package/template-react/package.json +25 -0
- package/template-react/public/react.svg +14 -0
- package/template-react/public/typescript.svg +16 -0
- package/template-react/public/vite.svg +42 -0
- package/template-react/resources.js +27 -0
- package/template-react/schema.graphql +5 -0
- package/template-react/src/App.jsx +34 -0
- package/template-react/src/main.jsx +13 -0
- package/template-react/src/style.css +96 -0
- package/template-react/src/vite-env.d.ts +9 -0
- package/template-react/vite.config.js +22 -0
- package/template-react-ts/README.md +45 -0
- package/template-react-ts/_aiignore +1 -0
- package/template-react-ts/_env +3 -0
- package/template-react-ts/_env.example +3 -0
- package/template-react-ts/_github/workflow/deploy.yaml +40 -0
- package/template-react-ts/_gitignore +147 -0
- package/template-react-ts/config.yaml +23 -0
- package/template-react-ts/deploy-template/config.yaml +2 -0
- package/template-react-ts/deploy-template/fastify/static.js +14 -0
- package/template-react-ts/deploy-template/package.json +5 -0
- package/template-react-ts/graphql.config.yml +3 -0
- package/template-react-ts/index.html +13 -0
- package/template-react-ts/package.json +29 -0
- package/template-react-ts/public/react.svg +14 -0
- package/template-react-ts/public/typescript.svg +16 -0
- package/template-react-ts/public/vite.svg +42 -0
- package/template-react-ts/resources.ts +52 -0
- package/template-react-ts/schema.graphql +5 -0
- package/template-react-ts/src/App.tsx +34 -0
- package/template-react-ts/src/main.tsx +13 -0
- package/template-react-ts/src/style.css +96 -0
- package/template-react-ts/src/vite-env.d.ts +9 -0
- package/template-react-ts/tsconfig.json +34 -0
- package/template-react-ts/vite.config.ts +22 -0
- package/template-studio/README.md +34 -0
- package/template-studio/_aiignore +1 -0
- package/template-studio/_gitignore +147 -0
- package/template-studio/config.yaml +24 -0
- package/template-studio/graphql.config.yml +3 -0
- package/template-studio/package.json +14 -0
- package/template-studio/resources.js +27 -0
- package/template-studio/schema.graphql +7 -0
- package/template-studio/web/index.html +28 -0
- package/template-studio/web/index.js +18 -0
- package/template-studio/web/styles.css +57 -0
- package/template-studio-ts/README.md +34 -0
- package/template-studio-ts/_aiignore +1 -0
- package/template-studio-ts/_gitignore +147 -0
- package/template-studio-ts/config.yaml +24 -0
- package/template-studio-ts/graphql.config.yml +3 -0
- package/template-studio-ts/package.json +14 -0
- package/template-studio-ts/resources.ts +52 -0
- package/template-studio-ts/schema.graphql +7 -0
- package/template-studio-ts/tsconfig.json +10 -0
- package/template-studio-ts/web/index.html +28 -0
- package/template-studio-ts/web/index.js +18 -0
- package/template-studio-ts/web/styles.css +57 -0
- package/template-vanilla/README.md +57 -0
- package/template-vanilla/_aiignore +1 -0
- package/template-vanilla/_env +3 -0
- package/template-vanilla/_env.example +3 -0
- package/template-vanilla/_gitignore +147 -0
- package/template-vanilla/config.yaml +24 -0
- package/template-vanilla/graphql.config.yml +3 -0
- package/template-vanilla/package.json +14 -0
- package/template-vanilla/resources.js +27 -0
- package/template-vanilla/schema.graphql +7 -0
- package/template-vanilla/web/index.html +28 -0
- package/template-vanilla/web/index.js +18 -0
- package/template-vanilla/web/styles.css +57 -0
- package/template-vanilla-ts/README.md +57 -0
- package/template-vanilla-ts/_aiignore +1 -0
- package/template-vanilla-ts/_env +3 -0
- package/template-vanilla-ts/_env.example +3 -0
- package/template-vanilla-ts/_gitignore +147 -0
- package/template-vanilla-ts/config.yaml +24 -0
- package/template-vanilla-ts/graphql.config.yml +3 -0
- package/template-vanilla-ts/package.json +14 -0
- package/template-vanilla-ts/resources.ts +52 -0
- package/template-vanilla-ts/schema.graphql +7 -0
- package/template-vanilla-ts/tsconfig.json +10 -0
- package/template-vanilla-ts/web/index.html +28 -0
- package/template-vanilla-ts/web/index.js +18 -0
- package/template-vanilla-ts/web/styles.css +57 -0
package/README.md
CHANGED
|
@@ -78,3 +78,8 @@ Currently supported template presets include:
|
|
|
78
78
|
- `qwik-ts`
|
|
79
79
|
|
|
80
80
|
You can use `.` for the project name to scaffold in the current directory.
|
|
81
|
+
|
|
82
|
+
## Shout Out
|
|
83
|
+
|
|
84
|
+
This project is based largely on the prior work of the Vite team on Create Vite:
|
|
85
|
+
https://github.com/vitejs/vite/tree/main/packages/create-vite
|
package/index.js
CHANGED
|
@@ -1,43 +1,31 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import * as prompts from '@clack/prompts';
|
|
3
3
|
import { determineAgent } from '@vercel/detect-agent';
|
|
4
|
-
import spawn from 'cross-spawn';
|
|
5
4
|
import mri from 'mri';
|
|
6
|
-
import fs from 'node:fs';
|
|
7
|
-
import path from 'node:path';
|
|
8
|
-
import { fileURLToPath } from 'node:url';
|
|
9
|
-
import { defaultTargetDir } from './lib/constants/defaultTargetDir.js';
|
|
10
|
-
import { FRAMEWORKS } from './lib/constants/frameworks.js';
|
|
11
5
|
import { helpMessage } from './lib/constants/helpMessage.js';
|
|
12
|
-
import { TEMPLATES } from './lib/constants/templates.js';
|
|
13
|
-
import { crawlTemplateDir } from './lib/fs/crawlTemplateDir.js';
|
|
14
|
-
import { emptyDir } from './lib/fs/emptyDir.js';
|
|
15
6
|
import { formatTargetDir } from './lib/fs/formatTargetDir.js';
|
|
16
|
-
import { isEmpty } from './lib/fs/isEmpty.js';
|
|
17
|
-
import { install } from './lib/install.js';
|
|
18
|
-
import { getFullCustomCommand } from './lib/pkg/getFullCustomCommand.js';
|
|
19
|
-
import { getInstallCommand } from './lib/pkg/getInstallCommand.js';
|
|
20
|
-
import { getRunCommand } from './lib/pkg/getRunCommand.js';
|
|
21
|
-
import { isValidPackageName } from './lib/pkg/isValidPackageName.js';
|
|
22
7
|
import { pkgFromUserAgent } from './lib/pkg/pkgFromUserAgent.js';
|
|
23
|
-
import {
|
|
24
|
-
import {
|
|
8
|
+
import { getEnvVars } from './lib/steps/getEnvVars.js';
|
|
9
|
+
import { getImmediate } from './lib/steps/getImmediate.js';
|
|
10
|
+
import { getPackageName } from './lib/steps/getPackageName.js';
|
|
11
|
+
import { getProjectName } from './lib/steps/getProjectName.js';
|
|
12
|
+
import { getTemplate } from './lib/steps/getTemplate.js';
|
|
13
|
+
import { handleExistingDir } from './lib/steps/handleExistingDir.js';
|
|
14
|
+
import { scaffoldProject } from './lib/steps/scaffoldProject.js';
|
|
15
|
+
import { showOutro } from './lib/steps/showOutro.js';
|
|
25
16
|
|
|
26
17
|
const argv = mri(process.argv.slice(2), {
|
|
27
18
|
boolean: ['help', 'overwrite', 'immediate', 'interactive'],
|
|
28
19
|
alias: { h: 'help', t: 'template', i: 'immediate' },
|
|
29
|
-
string: ['template'],
|
|
20
|
+
string: ['template', 'cli-target-username', 'cli-target'],
|
|
30
21
|
});
|
|
31
|
-
const cwd = process.cwd();
|
|
32
22
|
|
|
33
23
|
init().catch((e) => {
|
|
34
24
|
console.error(e);
|
|
35
25
|
});
|
|
36
26
|
|
|
37
27
|
async function init() {
|
|
38
|
-
const argTargetDir = argv._[0]
|
|
39
|
-
? formatTargetDir(String(argv._[0]))
|
|
40
|
-
: undefined;
|
|
28
|
+
const argTargetDir = argv._[0] ? formatTargetDir(String(argv._[0])) : undefined;
|
|
41
29
|
const argTemplate = argv.template;
|
|
42
30
|
const argOverwrite = argv.overwrite;
|
|
43
31
|
const argImmediate = argv.immediate;
|
|
@@ -59,207 +47,42 @@ async function init() {
|
|
|
59
47
|
);
|
|
60
48
|
}
|
|
61
49
|
|
|
62
|
-
const pkgInfo = pkgFromUserAgent(process.env.npm_config_user_agent);
|
|
63
50
|
const cancel = () => prompts.cancel('Operation cancelled');
|
|
64
51
|
|
|
65
|
-
// 1. Get project name and target
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
if (interactive) {
|
|
70
|
-
projectName = await prompts.text({
|
|
71
|
-
message: 'Project name:',
|
|
72
|
-
defaultValue: defaultTargetDir,
|
|
73
|
-
placeholder: defaultTargetDir,
|
|
74
|
-
validate: (value) => {
|
|
75
|
-
return !value || formatTargetDir(value).length > 0
|
|
76
|
-
? undefined
|
|
77
|
-
: 'Invalid project name';
|
|
78
|
-
},
|
|
79
|
-
});
|
|
80
|
-
if (prompts.isCancel(projectName)) { return cancel(); }
|
|
81
|
-
targetDir = formatTargetDir(projectName);
|
|
82
|
-
} else {
|
|
83
|
-
targetDir = defaultTargetDir;
|
|
84
|
-
}
|
|
85
|
-
}
|
|
52
|
+
// 1. Get the project name and target directory
|
|
53
|
+
const projectNameResult = await getProjectName(argTargetDir, interactive);
|
|
54
|
+
if (projectNameResult.cancelled) { return cancel(); }
|
|
55
|
+
const { projectName, targetDir } = projectNameResult;
|
|
86
56
|
|
|
87
|
-
// 2. Handle directory
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
? 'yes'
|
|
91
|
-
: undefined;
|
|
92
|
-
if (!overwrite) {
|
|
93
|
-
if (interactive) {
|
|
94
|
-
const res = await prompts.select({
|
|
95
|
-
message: (targetDir === '.'
|
|
96
|
-
? 'Current directory'
|
|
97
|
-
: `Target directory "${targetDir}"`)
|
|
98
|
-
+ ` is not empty. Please choose how to proceed:`,
|
|
99
|
-
options: [
|
|
100
|
-
{
|
|
101
|
-
label: 'Cancel operation',
|
|
102
|
-
value: 'no',
|
|
103
|
-
},
|
|
104
|
-
{
|
|
105
|
-
label: 'Remove existing files and continue',
|
|
106
|
-
value: 'yes',
|
|
107
|
-
},
|
|
108
|
-
{
|
|
109
|
-
label: 'Ignore files and continue',
|
|
110
|
-
value: 'ignore',
|
|
111
|
-
},
|
|
112
|
-
],
|
|
113
|
-
});
|
|
114
|
-
if (prompts.isCancel(res)) { return cancel(); }
|
|
115
|
-
overwrite = res;
|
|
116
|
-
} else {
|
|
117
|
-
overwrite = 'no';
|
|
118
|
-
}
|
|
119
|
-
}
|
|
57
|
+
// 2. Handle if the directory exists and isn't empty
|
|
58
|
+
const handleExistingDirResult = await handleExistingDir(targetDir, argOverwrite, interactive);
|
|
59
|
+
if (handleExistingDirResult.cancelled) { return cancel(); }
|
|
120
60
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
case 'no':
|
|
126
|
-
cancel();
|
|
127
|
-
return;
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
// 3. Get package name
|
|
132
|
-
let packageName = path.basename(path.resolve(targetDir));
|
|
133
|
-
if (!isValidPackageName(packageName)) {
|
|
134
|
-
if (interactive) {
|
|
135
|
-
const packageNameResult = await prompts.text({
|
|
136
|
-
message: 'Package name:',
|
|
137
|
-
defaultValue: toValidPackageName(packageName),
|
|
138
|
-
placeholder: toValidPackageName(packageName),
|
|
139
|
-
validate(dir) {
|
|
140
|
-
if (dir && !isValidPackageName(dir)) {
|
|
141
|
-
return 'Invalid package.json name';
|
|
142
|
-
}
|
|
143
|
-
},
|
|
144
|
-
});
|
|
145
|
-
if (prompts.isCancel(packageNameResult)) { return cancel(); }
|
|
146
|
-
packageName = packageNameResult;
|
|
147
|
-
} else {
|
|
148
|
-
packageName = toValidPackageName(packageName);
|
|
149
|
-
}
|
|
150
|
-
}
|
|
61
|
+
// 3. Get the package name
|
|
62
|
+
const packageNameResult = await getPackageName(targetDir, interactive);
|
|
63
|
+
if (packageNameResult.cancelled) { return cancel(); }
|
|
64
|
+
const { packageName } = packageNameResult;
|
|
151
65
|
|
|
152
66
|
// 4. Choose a framework and variant
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
template = undefined;
|
|
157
|
-
hasInvalidArgTemplate = true;
|
|
158
|
-
}
|
|
159
|
-
if (!template) {
|
|
160
|
-
if (interactive) {
|
|
161
|
-
const framework = await prompts.select({
|
|
162
|
-
message: hasInvalidArgTemplate
|
|
163
|
-
? `"${argTemplate}" isn't a valid template. Please choose from below: `
|
|
164
|
-
: 'Select a framework:',
|
|
165
|
-
options: FRAMEWORKS
|
|
166
|
-
.filter(framework => !framework.hidden)
|
|
167
|
-
.map((framework) => {
|
|
168
|
-
const frameworkColor = framework.color;
|
|
169
|
-
return {
|
|
170
|
-
label: frameworkColor(framework.display || framework.name),
|
|
171
|
-
value: framework,
|
|
172
|
-
};
|
|
173
|
-
}),
|
|
174
|
-
});
|
|
175
|
-
if (prompts.isCancel(framework)) { return cancel(); }
|
|
176
|
-
|
|
177
|
-
const variant = framework.variants.length === 1
|
|
178
|
-
? framework.variants[0].name
|
|
179
|
-
: await prompts.select({
|
|
180
|
-
message: 'Select a variant:',
|
|
181
|
-
options: framework.variants.map((variant) => {
|
|
182
|
-
const variantColor = variant.color;
|
|
183
|
-
const command = variant.customCommand
|
|
184
|
-
? getFullCustomCommand(variant.customCommand, pkgInfo).replace(
|
|
185
|
-
/ TARGET_DIR$/,
|
|
186
|
-
'',
|
|
187
|
-
)
|
|
188
|
-
: undefined;
|
|
189
|
-
return {
|
|
190
|
-
label: variantColor(variant.display || variant.name),
|
|
191
|
-
value: variant.name,
|
|
192
|
-
hint: command,
|
|
193
|
-
};
|
|
194
|
-
}),
|
|
195
|
-
});
|
|
196
|
-
if (prompts.isCancel(variant)) { return cancel(); }
|
|
197
|
-
|
|
198
|
-
template = variant;
|
|
199
|
-
} else {
|
|
200
|
-
template = 'vanilla-ts';
|
|
201
|
-
}
|
|
202
|
-
}
|
|
67
|
+
const templateResult = await getTemplate(argTemplate, interactive);
|
|
68
|
+
if (templateResult.cancelled) { return cancel(); }
|
|
69
|
+
const { template } = templateResult;
|
|
203
70
|
|
|
71
|
+
// 5. Should we do a package manager installation?
|
|
72
|
+
const pkgInfo = pkgFromUserAgent(process.env.npm_config_user_agent);
|
|
204
73
|
const pkgManager = pkgInfo ? pkgInfo.name : 'npm';
|
|
74
|
+
const immediateResult = await getImmediate(argImmediate, interactive, pkgManager);
|
|
75
|
+
if (immediateResult.cancelled) { return cancel(); }
|
|
76
|
+
const { immediate } = immediateResult;
|
|
205
77
|
|
|
206
|
-
|
|
78
|
+
// 6. Get environment variables for .env file
|
|
79
|
+
const envVarsResult = await getEnvVars(argv, interactive, template);
|
|
80
|
+
if (envVarsResult.cancelled) { return cancel(); }
|
|
81
|
+
const { envVars } = envVarsResult;
|
|
207
82
|
|
|
208
|
-
|
|
83
|
+
// 7. Write out the contents based on all prior steps.
|
|
84
|
+
const root = scaffoldProject(targetDir, projectName, packageName, template, envVars);
|
|
209
85
|
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
const [command, ...args] = fullCustomCommand.split(' ');
|
|
214
|
-
// we replace TARGET_DIR here because targetDir may include a space
|
|
215
|
-
const replacedArgs = args.map((arg) => arg.replace('TARGET_DIR', () => targetDir));
|
|
216
|
-
const { status } = spawn.sync(command, replacedArgs, {
|
|
217
|
-
stdio: 'inherit',
|
|
218
|
-
});
|
|
219
|
-
process.exit(status ?? 0);
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
// 5. Ask about immediate install and package manager
|
|
223
|
-
let immediate = argImmediate;
|
|
224
|
-
if (immediate === undefined) {
|
|
225
|
-
if (interactive) {
|
|
226
|
-
const immediateResult = await prompts.confirm({
|
|
227
|
-
message: `Install with ${pkgManager} and start now?`,
|
|
228
|
-
});
|
|
229
|
-
if (prompts.isCancel(immediateResult)) { return cancel(); }
|
|
230
|
-
immediate = immediateResult;
|
|
231
|
-
} else {
|
|
232
|
-
immediate = false;
|
|
233
|
-
}
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
// Only create a directory for built-in templates, not for customCommand
|
|
237
|
-
fs.mkdirSync(root, { recursive: true });
|
|
238
|
-
prompts.log.step(`Scaffolding project in ${root}...`);
|
|
239
|
-
|
|
240
|
-
const context = {
|
|
241
|
-
projectName,
|
|
242
|
-
packageName,
|
|
243
|
-
};
|
|
244
|
-
|
|
245
|
-
const templateSharedDir = path.resolve(fileURLToPath(import.meta.url), '..', `template-shared`);
|
|
246
|
-
crawlTemplateDir(root, templateSharedDir, context);
|
|
247
|
-
|
|
248
|
-
const templateDir = path.resolve(fileURLToPath(import.meta.url), '..', `template-${template}`);
|
|
249
|
-
crawlTemplateDir(root, templateDir, context);
|
|
250
|
-
|
|
251
|
-
if (immediate) {
|
|
252
|
-
install(root, pkgManager);
|
|
253
|
-
start(root, pkgManager);
|
|
254
|
-
} else {
|
|
255
|
-
let doneMessage = '';
|
|
256
|
-
const cdProjectName = path.relative(cwd, root);
|
|
257
|
-
doneMessage += `Done. Now run:\n`;
|
|
258
|
-
if (root !== cwd) {
|
|
259
|
-
doneMessage += `\n cd ${cdProjectName.includes(' ') ? `"${cdProjectName}"` : cdProjectName}`;
|
|
260
|
-
}
|
|
261
|
-
doneMessage += `\n ${getInstallCommand(pkgManager).join(' ')}`;
|
|
262
|
-
doneMessage += `\n ${getRunCommand(pkgManager, 'dev').join(' ')}`;
|
|
263
|
-
prompts.outro(doneMessage);
|
|
264
|
-
}
|
|
86
|
+
// 8. Log out the next steps.
|
|
87
|
+
showOutro(root, pkgManager, immediate);
|
|
265
88
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const defaultTargetDir = 'harper-project';
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import colors from 'picocolors';
|
|
2
|
+
|
|
3
|
+
const {
|
|
4
|
+
blue,
|
|
5
|
+
cyan,
|
|
6
|
+
gray,
|
|
7
|
+
yellow,
|
|
8
|
+
} = colors;
|
|
9
|
+
|
|
10
|
+
export const FRAMEWORKS = [
|
|
11
|
+
{
|
|
12
|
+
name: 'vanilla',
|
|
13
|
+
display: 'Vanilla',
|
|
14
|
+
color: yellow,
|
|
15
|
+
variants: [
|
|
16
|
+
{
|
|
17
|
+
name: 'vanilla-ts',
|
|
18
|
+
display: 'TypeScript',
|
|
19
|
+
color: blue,
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
name: 'vanilla',
|
|
23
|
+
display: 'JavaScript',
|
|
24
|
+
color: yellow,
|
|
25
|
+
},
|
|
26
|
+
],
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
name: 'react',
|
|
30
|
+
display: 'React',
|
|
31
|
+
color: cyan,
|
|
32
|
+
variants: [
|
|
33
|
+
{
|
|
34
|
+
name: 'react-ts',
|
|
35
|
+
display: 'TypeScript',
|
|
36
|
+
color: blue,
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
name: 'react',
|
|
40
|
+
display: 'JavaScript',
|
|
41
|
+
color: yellow,
|
|
42
|
+
},
|
|
43
|
+
],
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
name: 'studio',
|
|
47
|
+
hidden: true,
|
|
48
|
+
display: 'Studio',
|
|
49
|
+
color: gray,
|
|
50
|
+
variants: [
|
|
51
|
+
{
|
|
52
|
+
name: 'studio-ts',
|
|
53
|
+
display: 'TypeScript',
|
|
54
|
+
color: gray,
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
name: 'studio',
|
|
58
|
+
display: 'JavaScript',
|
|
59
|
+
color: gray,
|
|
60
|
+
},
|
|
61
|
+
],
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
name: 'barebones',
|
|
65
|
+
display: 'Barebones',
|
|
66
|
+
color: gray,
|
|
67
|
+
variants: [
|
|
68
|
+
{
|
|
69
|
+
name: 'barebones',
|
|
70
|
+
display: 'Barebones',
|
|
71
|
+
color: gray,
|
|
72
|
+
},
|
|
73
|
+
],
|
|
74
|
+
},
|
|
75
|
+
];
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import colors from 'picocolors';
|
|
2
|
+
|
|
3
|
+
const {
|
|
4
|
+
cyan,
|
|
5
|
+
green,
|
|
6
|
+
yellow,
|
|
7
|
+
} = colors;
|
|
8
|
+
|
|
9
|
+
export const helpMessage = `\
|
|
10
|
+
Usage: create-harper [OPTION]... [DIRECTORY]
|
|
11
|
+
|
|
12
|
+
Create a new Harper project in JavaScript or TypeScript.
|
|
13
|
+
When running in TTY, the CLI will start in interactive mode.
|
|
14
|
+
|
|
15
|
+
Options:
|
|
16
|
+
-t, --template NAME use a specific template
|
|
17
|
+
-i, --immediate install dependencies and start dev
|
|
18
|
+
--interactive / --no-interactive force interactive / non-interactive mode
|
|
19
|
+
|
|
20
|
+
Available templates:
|
|
21
|
+
${yellow('vanilla-ts vanilla')}
|
|
22
|
+
${green('vue-ts vue')}
|
|
23
|
+
${cyan('react-ts react')}`;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
|
|
3
|
+
export function applyAndWriteTemplateFile(targetPath, templatePath, substitutions) {
|
|
4
|
+
let updatedContent = fs.readFileSync(templatePath, 'utf-8');
|
|
5
|
+
for (const variableName in substitutions) {
|
|
6
|
+
updatedContent = updatedContent.replaceAll(variableName, substitutions[variableName]);
|
|
7
|
+
}
|
|
8
|
+
fs.writeFileSync(targetPath, updatedContent);
|
|
9
|
+
}
|
package/lib/fs/copy.js
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { copy } from './copy.js';
|
|
4
|
+
|
|
5
|
+
export function copyDir(srcDir, destDir) {
|
|
6
|
+
fs.mkdirSync(destDir, { recursive: true });
|
|
7
|
+
for (const file of fs.readdirSync(srcDir)) {
|
|
8
|
+
const srcFile = path.resolve(srcDir, file);
|
|
9
|
+
const destFile = path.resolve(destDir, file);
|
|
10
|
+
copy(srcFile, destFile);
|
|
11
|
+
}
|
|
12
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { renameFiles } from '../constants/renameFiles.js';
|
|
4
|
+
import { applyAndWriteTemplateFile } from './applyAndWriteTemplateFile.js';
|
|
5
|
+
|
|
6
|
+
export function crawlTemplateDir(root, dir, substitutions) {
|
|
7
|
+
const files = fs.readdirSync(dir);
|
|
8
|
+
for (const file of files) {
|
|
9
|
+
const targetPath = path.join(root, renameFiles[file] ?? file);
|
|
10
|
+
const templatePath = path.join(dir, file);
|
|
11
|
+
if (fs.lstatSync(templatePath).isDirectory()) {
|
|
12
|
+
fs.mkdirSync(targetPath, { recursive: true });
|
|
13
|
+
crawlTemplateDir(targetPath, templatePath, substitutions);
|
|
14
|
+
} else {
|
|
15
|
+
applyAndWriteTemplateFile(targetPath, templatePath, substitutions);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
|
|
4
|
+
export function emptyDir(dir) {
|
|
5
|
+
if (!fs.existsSync(dir)) {
|
|
6
|
+
return;
|
|
7
|
+
}
|
|
8
|
+
for (const file of fs.readdirSync(dir)) {
|
|
9
|
+
if (file === '.git') {
|
|
10
|
+
continue;
|
|
11
|
+
}
|
|
12
|
+
fs.rmSync(path.resolve(dir, file), { recursive: true, force: true });
|
|
13
|
+
}
|
|
14
|
+
}
|
package/lib/install.js
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import * as prompts from '@clack/prompts';
|
|
2
|
+
import { getInstallCommand } from './pkg/getInstallCommand.js';
|
|
3
|
+
import { run } from './run.js';
|
|
4
|
+
|
|
5
|
+
export function install(root, agent) {
|
|
6
|
+
if (process.env._HARPER_TEST_CLI) {
|
|
7
|
+
prompts.log.step(
|
|
8
|
+
`Installing dependencies with ${agent}... (skipped in test)`,
|
|
9
|
+
);
|
|
10
|
+
return;
|
|
11
|
+
}
|
|
12
|
+
prompts.log.step(`Installing dependencies with ${agent}...`);
|
|
13
|
+
run(getInstallCommand(agent), {
|
|
14
|
+
stdio: 'inherit',
|
|
15
|
+
cwd: root,
|
|
16
|
+
});
|
|
17
|
+
}
|
package/lib/run.js
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import spawn from 'cross-spawn';
|
|
2
|
+
|
|
3
|
+
export function run([command, ...args], options) {
|
|
4
|
+
const { status, error } = spawn.sync(command, args, options);
|
|
5
|
+
if (status != null && status > 0) {
|
|
6
|
+
process.exit(status);
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
if (error) {
|
|
10
|
+
console.error(`\n${command} ${args.join(' ')} error!`);
|
|
11
|
+
console.error(error);
|
|
12
|
+
process.exit(1);
|
|
13
|
+
}
|
|
14
|
+
}
|
package/lib/start.js
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import * as prompts from '@clack/prompts';
|
|
2
|
+
import { getRunCommand } from './pkg/getRunCommand.js';
|
|
3
|
+
import { run } from './run.js';
|
|
4
|
+
|
|
5
|
+
export function start(root, agent) {
|
|
6
|
+
if (process.env._HARPER_TEST_CLI) {
|
|
7
|
+
prompts.log.step('Starting dev server... (skipped in test)');
|
|
8
|
+
return;
|
|
9
|
+
}
|
|
10
|
+
prompts.log.step('Starting dev server...');
|
|
11
|
+
run(getRunCommand(agent, 'dev'), {
|
|
12
|
+
stdio: 'inherit',
|
|
13
|
+
cwd: root,
|
|
14
|
+
});
|
|
15
|
+
}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import * as prompts from '@clack/prompts';
|
|
2
|
+
import fs from 'node:fs';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { fileURLToPath } from 'node:url';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Step 5: Get environment variables for .env file
|
|
8
|
+
* @param {any} argv
|
|
9
|
+
* @param {boolean} interactive
|
|
10
|
+
* @param {string} template
|
|
11
|
+
* @returns {Promise<{envVars: {username: string, target: string, password?: string}, cancelled: boolean}>}
|
|
12
|
+
*/
|
|
13
|
+
export async function getEnvVars(argv, interactive, template) {
|
|
14
|
+
const templateDir = path.resolve(
|
|
15
|
+
fileURLToPath(import.meta.url),
|
|
16
|
+
'..',
|
|
17
|
+
'..',
|
|
18
|
+
'..',
|
|
19
|
+
`template-${template}`,
|
|
20
|
+
);
|
|
21
|
+
const hasEnvFile = fs.existsSync(path.join(templateDir, '_env'));
|
|
22
|
+
|
|
23
|
+
if (!hasEnvFile) {
|
|
24
|
+
return {
|
|
25
|
+
envVars: {},
|
|
26
|
+
cancelled: false,
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
let username = argv['cli-target-username'];
|
|
31
|
+
let target = argv['cli-target'];
|
|
32
|
+
let password = '';
|
|
33
|
+
|
|
34
|
+
if (interactive) {
|
|
35
|
+
if (!username) {
|
|
36
|
+
const usernameResult = await prompts.text({
|
|
37
|
+
message: 'CLI Target Username:',
|
|
38
|
+
placeholder: 'YOUR_CLUSTER_USERNAME',
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
if (prompts.isCancel(usernameResult)) {
|
|
42
|
+
return { envVars: {}, cancelled: true };
|
|
43
|
+
}
|
|
44
|
+
username = usernameResult;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
if (!target) {
|
|
48
|
+
const targetResult = await prompts.text({
|
|
49
|
+
message: 'CLI Target URL:',
|
|
50
|
+
placeholder: 'YOUR_FABRIC.HARPER.FAST_CLUSTER_URL_HERE',
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
if (prompts.isCancel(targetResult)) {
|
|
54
|
+
return { envVars: {}, cancelled: true };
|
|
55
|
+
}
|
|
56
|
+
target = targetResult;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const passwordResult = await prompts.password({
|
|
60
|
+
message: 'CLI Target Password:',
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
if (prompts.isCancel(passwordResult)) {
|
|
64
|
+
return { envVars: {}, cancelled: true };
|
|
65
|
+
}
|
|
66
|
+
password = passwordResult;
|
|
67
|
+
} else {
|
|
68
|
+
prompts.log.warn('Non-interactive mode: Please update your .env to add your CLI_TARGET_PASSWORD on your own.');
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
return {
|
|
72
|
+
envVars: {
|
|
73
|
+
username: username || 'YOUR_CLUSTER_USERNAME',
|
|
74
|
+
target: target || 'YOUR_FABRIC.HARPER.FAST_CLUSTER_URL_HERE',
|
|
75
|
+
password: password || 'YOUR_CLUSTER_PASSWORD',
|
|
76
|
+
},
|
|
77
|
+
cancelled: false,
|
|
78
|
+
};
|
|
79
|
+
}
|