create-nattyjs 0.0.1-beta.69 → 0.0.1-beta.71
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/index.js +92 -349
- package/package.json +4 -3
- package/src/const/template-definitions.js +76 -0
- package/src/const/version-registry.js +73 -0
- package/src/const/workspace-definitions.js +55 -0
- package/src/functions/cli-theme.js +81 -0
- package/src/functions/file-system.js +96 -0
- package/src/functions/package-manager.js +9 -0
- package/src/functions/package-name.js +12 -0
- package/src/functions/prompt-project-options.js +156 -0
- package/src/functions/render-package-jsons.js +98 -0
- package/src/functions/render-workspace.js +254 -0
- package/src/functions/scaffold-project.js +65 -0
- package/template-fullstack-angular/apps/api/package.json +8 -8
- package/template-fullstack-angular/apps/web/package.json +1 -1
- package/template-fullstack-astro/apps/api/package.json +8 -8
- package/template-fullstack-astro/apps/web/package.json +1 -1
- package/template-fullstack-next/apps/api/package.json +8 -8
- package/template-fullstack-next/apps/web/package.json +1 -1
- package/template-fullstack-nuxt/apps/api/package.json +8 -8
- package/template-fullstack-nuxt/apps/web/package.json +1 -1
- package/template-fullstack-react/apps/api/package.json +8 -8
- package/template-fullstack-react/apps/web/package.json +1 -1
- package/template-fullstack-sveltekit/apps/api/package.json +8 -8
- package/template-fullstack-sveltekit/apps/web/package.json +1 -1
- package/template-fullstack-vue/apps/api/package.json +8 -8
- package/template-fullstack-vue/apps/web/package.json +1 -1
- package/template-nattyjs-blank/package.json +8 -8
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import path from "path";
|
|
2
|
+
import { bold, cyan, dim, green, yellow } from "kolorist";
|
|
3
|
+
|
|
4
|
+
export function printIntro() {
|
|
5
|
+
console.log("");
|
|
6
|
+
console.log(bold(cyan("NattyJS Create")));
|
|
7
|
+
console.log(dim("Scaffold a typed API or a fullstack workspace."));
|
|
8
|
+
console.log("");
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function formatTemplateChoice(template) {
|
|
12
|
+
const titleColor = template.id === "api-only" ? green : cyan;
|
|
13
|
+
const badge = template.id === "api-only" ? green("API") : cyan("FULLSTACK");
|
|
14
|
+
return {
|
|
15
|
+
title: `${bold(titleColor(template.title))} ${dim(badge)}`,
|
|
16
|
+
description: dim(template.description),
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function formatLayoutChoice(layout) {
|
|
21
|
+
const isRecommended = layout.recommended === true;
|
|
22
|
+
const title = isRecommended
|
|
23
|
+
? `${bold(green(layout.title))} ${dim("(recommended)")}`
|
|
24
|
+
: bold(cyan(layout.title));
|
|
25
|
+
|
|
26
|
+
return {
|
|
27
|
+
title,
|
|
28
|
+
description: dim(layout.description),
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function formatWorkspaceChoice(workspace) {
|
|
33
|
+
const isRecommended = workspace.recommended === true;
|
|
34
|
+
const title = isRecommended
|
|
35
|
+
? `${bold(yellow(workspace.title))} ${dim("(recommended)")}`
|
|
36
|
+
: bold(cyan(workspace.title));
|
|
37
|
+
|
|
38
|
+
return {
|
|
39
|
+
title,
|
|
40
|
+
description: dim(workspace.description),
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function formatSuccessSummary({
|
|
45
|
+
cwd,
|
|
46
|
+
root,
|
|
47
|
+
packageManager,
|
|
48
|
+
template,
|
|
49
|
+
workspace,
|
|
50
|
+
includeExamples,
|
|
51
|
+
}) {
|
|
52
|
+
const activePackageManager = packageManager;
|
|
53
|
+
const relativeRoot = root === cwd ? "." : path.relative(cwd, root);
|
|
54
|
+
const isStandalone = workspace?.id === "standalone";
|
|
55
|
+
|
|
56
|
+
console.log("");
|
|
57
|
+
console.log(`${bold(green("Success"))} ${dim("Project scaffolded.")}`);
|
|
58
|
+
console.log(`${bold("Location")} ${relativeRoot}`);
|
|
59
|
+
console.log(`${bold("Template")} ${template.title}`);
|
|
60
|
+
if (workspace) {
|
|
61
|
+
console.log(`${bold("Layout")} ${workspace.title}`);
|
|
62
|
+
}
|
|
63
|
+
console.log(`${bold("Examples")} ${includeExamples ? "Yes" : "No"}`);
|
|
64
|
+
console.log("");
|
|
65
|
+
console.log(bold(cyan("Next Steps")));
|
|
66
|
+
if (root !== cwd) {
|
|
67
|
+
console.log(` cd ${relativeRoot}`);
|
|
68
|
+
}
|
|
69
|
+
if (activePackageManager === "yarn") {
|
|
70
|
+
console.log(" yarn");
|
|
71
|
+
console.log(" yarn dev");
|
|
72
|
+
console.log("");
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
console.log(` ${activePackageManager} install`);
|
|
76
|
+
if (isStandalone) {
|
|
77
|
+
console.log(` ${activePackageManager} run install:apps`);
|
|
78
|
+
}
|
|
79
|
+
console.log(` ${activePackageManager} run dev`);
|
|
80
|
+
console.log("");
|
|
81
|
+
}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import fs from "fs";
|
|
2
|
+
import path from "path";
|
|
3
|
+
|
|
4
|
+
const renameFiles = {
|
|
5
|
+
_gitignore: ".gitignore",
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
export function readJson(filePath) {
|
|
9
|
+
if (!fs.existsSync(filePath)) {
|
|
10
|
+
return undefined;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
return JSON.parse(fs.readFileSync(filePath, "utf-8"));
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function isEmpty(dirPath) {
|
|
17
|
+
const files = fs.readdirSync(dirPath);
|
|
18
|
+
return files.length === 0 || (files.length === 1 && files[0] === ".git");
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function emptyDir(dir) {
|
|
22
|
+
if (!fs.existsSync(dir)) {
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
for (const file of fs.readdirSync(dir)) {
|
|
27
|
+
const absolutePath = path.resolve(dir, file);
|
|
28
|
+
if (fs.lstatSync(absolutePath).isDirectory()) {
|
|
29
|
+
emptyDir(absolutePath);
|
|
30
|
+
fs.rmdirSync(absolutePath);
|
|
31
|
+
} else {
|
|
32
|
+
fs.unlinkSync(absolutePath);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function copy(src, dest) {
|
|
38
|
+
const stat = fs.statSync(src);
|
|
39
|
+
if (stat.isDirectory()) {
|
|
40
|
+
copyDir(src, dest);
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
fs.copyFileSync(src, dest);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function copyDir(srcDir, destDir) {
|
|
48
|
+
fs.mkdirSync(destDir, { recursive: true });
|
|
49
|
+
for (const file of fs.readdirSync(srcDir)) {
|
|
50
|
+
const srcFile = path.resolve(srcDir, file);
|
|
51
|
+
const destFile = path.resolve(destDir, file);
|
|
52
|
+
copy(srcFile, destFile);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export function shouldSkipTemplateFile(relativePath, template, includeExamples) {
|
|
57
|
+
if (includeExamples) {
|
|
58
|
+
return false;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const excludedPaths = template.exampleExcludedPaths || [];
|
|
62
|
+
return excludedPaths.some((excludedPath) => {
|
|
63
|
+
return relativePath === excludedPath || relativePath.startsWith(`${excludedPath}${path.sep}`);
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export function copyTemplateContents(templateDir, root, template, includeExamples) {
|
|
68
|
+
const entries = fs.readdirSync(templateDir);
|
|
69
|
+
for (const entry of entries) {
|
|
70
|
+
if (entry === "package.json") {
|
|
71
|
+
continue;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if (entry === template.exampleOverlayDir) {
|
|
75
|
+
continue;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
if (shouldSkipTemplateFile(entry, template, includeExamples)) {
|
|
79
|
+
continue;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const sourcePath = path.join(templateDir, entry);
|
|
83
|
+
const targetPath = renameFiles[entry]
|
|
84
|
+
? path.join(root, renameFiles[entry])
|
|
85
|
+
: path.join(root, entry);
|
|
86
|
+
|
|
87
|
+
copy(sourcePath, targetPath);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
if (includeExamples && template.exampleOverlayDir) {
|
|
91
|
+
const overlayDir = path.join(templateDir, template.exampleOverlayDir);
|
|
92
|
+
if (fs.existsSync(overlayDir)) {
|
|
93
|
+
copyDir(overlayDir, root);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export function isValidPackageName(projectName) {
|
|
2
|
+
return /^(?:@[a-z0-9-*~][a-z0-9-*._~]*\/)?[a-z0-9-~][a-z0-9-._~]*$/.test(projectName);
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
export function toValidPackageName(projectName) {
|
|
6
|
+
return projectName
|
|
7
|
+
.trim()
|
|
8
|
+
.toLowerCase()
|
|
9
|
+
.replace(/\s+/g, "-")
|
|
10
|
+
.replace(/^[._]/, "")
|
|
11
|
+
.replace(/[^a-z0-9-~]+/g, "-");
|
|
12
|
+
}
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
import fs from "fs";
|
|
2
|
+
import path from "path";
|
|
3
|
+
import prompts from "prompts";
|
|
4
|
+
import { red, reset } from "kolorist";
|
|
5
|
+
import {
|
|
6
|
+
getWorkspaceById,
|
|
7
|
+
isFullstackTemplate,
|
|
8
|
+
MONOREPO_WORKSPACE_DEFINITIONS,
|
|
9
|
+
PROJECT_LAYOUT_DEFINITIONS,
|
|
10
|
+
} from "../const/workspace-definitions.js";
|
|
11
|
+
import { formatLayoutChoice, formatTemplateChoice, formatWorkspaceChoice } from "./cli-theme.js";
|
|
12
|
+
import { isEmpty } from "./file-system.js";
|
|
13
|
+
import { isValidPackageName, toValidPackageName } from "./package-name.js";
|
|
14
|
+
|
|
15
|
+
export async function resolveProjectOptions({
|
|
16
|
+
argv,
|
|
17
|
+
defaultProjectName,
|
|
18
|
+
selectedTemplate,
|
|
19
|
+
templateDefinitions,
|
|
20
|
+
examplesFlagProvided,
|
|
21
|
+
}) {
|
|
22
|
+
let targetDir = typeof argv._[0] === "string" ? argv._[0].trim() : "";
|
|
23
|
+
let selectedWorkspace = getWorkspaceById(argv.workspace);
|
|
24
|
+
let activeTemplate = selectedTemplate;
|
|
25
|
+
|
|
26
|
+
try {
|
|
27
|
+
const response = await prompts(
|
|
28
|
+
[
|
|
29
|
+
{
|
|
30
|
+
type: targetDir ? null : "text",
|
|
31
|
+
name: "projectName",
|
|
32
|
+
message: reset("Project name:"),
|
|
33
|
+
initial: defaultProjectName,
|
|
34
|
+
validate: (value) => {
|
|
35
|
+
const trimmed = value.trim();
|
|
36
|
+
return trimmed.length > 0 || "Project name is required";
|
|
37
|
+
},
|
|
38
|
+
onState: (state) => {
|
|
39
|
+
targetDir = state.value.trim() || defaultProjectName;
|
|
40
|
+
},
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
type: () => {
|
|
44
|
+
return !targetDir || !fs.existsSync(targetDir) || isEmpty(targetDir) ? null : "confirm";
|
|
45
|
+
},
|
|
46
|
+
name: "overwrite",
|
|
47
|
+
message: () => {
|
|
48
|
+
const label = targetDir === "." ? "Current directory" : `Target directory "${targetDir}"`;
|
|
49
|
+
return `${label} is not empty. Remove existing files and continue?`;
|
|
50
|
+
},
|
|
51
|
+
initial: false,
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
type: (_, { overwrite } = {}) => {
|
|
55
|
+
if (overwrite === false) {
|
|
56
|
+
throw new Error(`${red("x")} Operation cancelled`);
|
|
57
|
+
}
|
|
58
|
+
return null;
|
|
59
|
+
},
|
|
60
|
+
name: "overwriteChecker",
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
type: () => (isValidPackageName(path.basename(targetDir || defaultProjectName)) ? null : "text"),
|
|
64
|
+
name: "packageName",
|
|
65
|
+
message: reset("Package name:"),
|
|
66
|
+
initial: () => toValidPackageName(path.basename(targetDir || defaultProjectName)),
|
|
67
|
+
validate: (value) => isValidPackageName(value) || "Invalid package.json name",
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
type: selectedTemplate ? null : "select",
|
|
71
|
+
name: "templateId",
|
|
72
|
+
message: reset("Select a template:"),
|
|
73
|
+
choices: templateDefinitions.map((template) => {
|
|
74
|
+
const formattedChoice = formatTemplateChoice(template);
|
|
75
|
+
return {
|
|
76
|
+
title: formattedChoice.title,
|
|
77
|
+
description: formattedChoice.description,
|
|
78
|
+
value: template.id,
|
|
79
|
+
};
|
|
80
|
+
}),
|
|
81
|
+
onState: (state) => {
|
|
82
|
+
activeTemplate =
|
|
83
|
+
templateDefinitions.find((template) => template.id === state.value) ||
|
|
84
|
+
selectedTemplate;
|
|
85
|
+
},
|
|
86
|
+
},
|
|
87
|
+
{
|
|
88
|
+
type: (_, values) => {
|
|
89
|
+
activeTemplate =
|
|
90
|
+
selectedTemplate ||
|
|
91
|
+
templateDefinitions.find((template) => template.id === values.templateId);
|
|
92
|
+
return !selectedWorkspace && isFullstackTemplate(activeTemplate) ? "select" : null;
|
|
93
|
+
},
|
|
94
|
+
name: "projectLayout",
|
|
95
|
+
message: reset("Select project shape:"),
|
|
96
|
+
choices: PROJECT_LAYOUT_DEFINITIONS.map((layout) => {
|
|
97
|
+
const formattedChoice = formatLayoutChoice(layout);
|
|
98
|
+
return {
|
|
99
|
+
title: formattedChoice.title,
|
|
100
|
+
description: formattedChoice.description,
|
|
101
|
+
value: layout.id,
|
|
102
|
+
};
|
|
103
|
+
}),
|
|
104
|
+
initial: 0,
|
|
105
|
+
},
|
|
106
|
+
{
|
|
107
|
+
type: (_, values) => {
|
|
108
|
+
activeTemplate =
|
|
109
|
+
selectedTemplate ||
|
|
110
|
+
templateDefinitions.find((template) => template.id === values.templateId);
|
|
111
|
+
if (selectedWorkspace || !isFullstackTemplate(activeTemplate)) {
|
|
112
|
+
return null;
|
|
113
|
+
}
|
|
114
|
+
return values.projectLayout === "monorepo" ? "select" : null;
|
|
115
|
+
},
|
|
116
|
+
name: "workspaceSetup",
|
|
117
|
+
message: reset("Select monorepo tool:"),
|
|
118
|
+
choices: MONOREPO_WORKSPACE_DEFINITIONS.map((workspace) => {
|
|
119
|
+
const formattedChoice = formatWorkspaceChoice(workspace);
|
|
120
|
+
return {
|
|
121
|
+
title: formattedChoice.title,
|
|
122
|
+
description: formattedChoice.description,
|
|
123
|
+
value: workspace.id,
|
|
124
|
+
};
|
|
125
|
+
}),
|
|
126
|
+
initial: 0,
|
|
127
|
+
},
|
|
128
|
+
{
|
|
129
|
+
type: examplesFlagProvided ? null : "toggle",
|
|
130
|
+
name: "includeExamples",
|
|
131
|
+
message: reset("Include examples?"),
|
|
132
|
+
initial: true,
|
|
133
|
+
active: "Yes",
|
|
134
|
+
inactive: "No",
|
|
135
|
+
},
|
|
136
|
+
],
|
|
137
|
+
{
|
|
138
|
+
onCancel: () => {
|
|
139
|
+
throw new Error(`${red("x")} Operation cancelled`);
|
|
140
|
+
},
|
|
141
|
+
}
|
|
142
|
+
);
|
|
143
|
+
|
|
144
|
+
const resolvedTemplate =
|
|
145
|
+
activeTemplate ||
|
|
146
|
+
templateDefinitions.find((template) => template.id === response.templateId);
|
|
147
|
+
if (isFullstackTemplate(resolvedTemplate) && !selectedWorkspace && response.projectLayout === "standalone") {
|
|
148
|
+
response.workspaceSetup = "standalone";
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
return response;
|
|
152
|
+
} catch (error) {
|
|
153
|
+
console.log(error.message);
|
|
154
|
+
return undefined;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import fs from "fs";
|
|
2
|
+
import path from "path";
|
|
3
|
+
import { getVersionRegistry } from "../const/version-registry.js";
|
|
4
|
+
import { applyStandaloneSetup, applyWorkspaceSetup } from "./render-workspace.js";
|
|
5
|
+
|
|
6
|
+
const DEPENDENCY_FIELDS = [
|
|
7
|
+
"dependencies",
|
|
8
|
+
"devDependencies",
|
|
9
|
+
"peerDependencies",
|
|
10
|
+
"optionalDependencies",
|
|
11
|
+
];
|
|
12
|
+
|
|
13
|
+
function collectPackageJsonPaths(root) {
|
|
14
|
+
const packageJsonPaths = [];
|
|
15
|
+
|
|
16
|
+
function walk(currentPath) {
|
|
17
|
+
for (const entry of fs.readdirSync(currentPath, { withFileTypes: true })) {
|
|
18
|
+
if (entry.name === "node_modules") {
|
|
19
|
+
continue;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const absolutePath = path.join(currentPath, entry.name);
|
|
23
|
+
if (entry.isDirectory()) {
|
|
24
|
+
walk(absolutePath);
|
|
25
|
+
continue;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
if (entry.isFile() && entry.name === "package.json") {
|
|
29
|
+
packageJsonPaths.push(absolutePath);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
walk(root);
|
|
35
|
+
return packageJsonPaths;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function updateDependencyVersions(packageJson, versionRegistry) {
|
|
39
|
+
for (const fieldName of DEPENDENCY_FIELDS) {
|
|
40
|
+
const dependencyMap = packageJson[fieldName];
|
|
41
|
+
if (!dependencyMap) {
|
|
42
|
+
continue;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
for (const dependencyName of Object.keys(dependencyMap)) {
|
|
46
|
+
if (versionRegistry.versions[dependencyName]) {
|
|
47
|
+
dependencyMap[dependencyName] = versionRegistry.versions[dependencyName];
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export function renderProjectPackageJsons({
|
|
54
|
+
root,
|
|
55
|
+
packageBaseDir,
|
|
56
|
+
packageName,
|
|
57
|
+
isFullstack,
|
|
58
|
+
workspaceSetup,
|
|
59
|
+
}) {
|
|
60
|
+
const versionRegistry = getVersionRegistry(packageBaseDir);
|
|
61
|
+
if (isFullstack && workspaceSetup === "standalone") {
|
|
62
|
+
const rootPackageJsonPath = path.join(root, "package.json");
|
|
63
|
+
const rootPackageJson = JSON.parse(fs.readFileSync(rootPackageJsonPath, "utf8"));
|
|
64
|
+
updateDependencyVersions(rootPackageJson, versionRegistry);
|
|
65
|
+
rootPackageJson.name = packageName;
|
|
66
|
+
applyStandaloneSetup({
|
|
67
|
+
root,
|
|
68
|
+
rootPackageJson,
|
|
69
|
+
versionRegistry,
|
|
70
|
+
});
|
|
71
|
+
fs.writeFileSync(rootPackageJsonPath, JSON.stringify(rootPackageJson, null, 2), "utf8");
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const packageJsonPaths = collectPackageJsonPaths(root);
|
|
75
|
+
const rootPackageJsonPath = path.join(root, "package.json");
|
|
76
|
+
|
|
77
|
+
for (const packageJsonPath of packageJsonPaths) {
|
|
78
|
+
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, "utf8"));
|
|
79
|
+
|
|
80
|
+
updateDependencyVersions(packageJson, versionRegistry);
|
|
81
|
+
|
|
82
|
+
if (packageJsonPath === rootPackageJsonPath) {
|
|
83
|
+
packageJson.name = packageName;
|
|
84
|
+
if (isFullstack) {
|
|
85
|
+
if (workspaceSetup !== "standalone") {
|
|
86
|
+
applyWorkspaceSetup({
|
|
87
|
+
root,
|
|
88
|
+
rootPackageJson: packageJson,
|
|
89
|
+
workspaceSetup,
|
|
90
|
+
versionRegistry,
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
fs.writeFileSync(packageJsonPath, JSON.stringify(packageJson, null, 2), "utf8");
|
|
97
|
+
}
|
|
98
|
+
}
|