-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
7a9c560
commit 337c23a
Showing
65 changed files
with
361 additions
and
39 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2,6 +2,8 @@ node_modules | |
|
||
*.local | ||
|
||
packages/*/build | ||
|
||
sandbox/* | ||
!sandbox/.gitkeep | ||
|
||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,6 +1,6 @@ | ||
{ | ||
"$schema": "node_modules/lerna/schemas/lerna-schema.json", | ||
"version": "0.0.0", | ||
"packages": ["packages/*", "templates/*"], | ||
"packages": ["packages/*"], | ||
"npmClient": "yarn" | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -3,20 +3,23 @@ | |
"private": true, | ||
"packageManager": "[email protected]", | ||
"workspaces": [ | ||
"packages/*", | ||
"templates/*" | ||
"packages/*" | ||
], | ||
"scripts": { | ||
"dev": "lerna run dev", | ||
"build": "lerna run build", | ||
"lint": "lerna run lint", | ||
"preview": "lerna run preview" | ||
"preview": "lerna run preview", | ||
"clean": "lerna run clean" | ||
}, | ||
"devDependencies": { | ||
"@types/meow": "^6.0.0", | ||
"@typescript-eslint/eslint-plugin": "^7.15.0", | ||
"@typescript-eslint/parser": "^7.15.0", | ||
"eslint": "^8.57.0", | ||
"lerna": "^8.1.6", | ||
"meow": "^13.2.0", | ||
"prettier": "^3.3.3", | ||
"typescript": "^5.2.2", | ||
"vite": "^5.3.4" | ||
}, | ||
|
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2,6 +2,7 @@ | |
"name": "@carto/create-common", | ||
"packageManager": "[email protected]", | ||
"author": "Don McCurdy <[email protected]>", | ||
"version": "0.0.0", | ||
"license": "MIT", | ||
"publishConfig": { | ||
"access": "public", | ||
|
@@ -21,9 +22,9 @@ | |
} | ||
}, | ||
"browserslist": [ | ||
"defaults", | ||
"not IE 11", | ||
"node >= 18" | ||
"defaults", | ||
"not IE 11", | ||
"node >= 18" | ||
], | ||
"scripts": { | ||
"build": "microbundle --format cjs,modern --no-compress --define VERSION=$npm_package_version", | ||
|
@@ -36,9 +37,11 @@ | |
}, | ||
"dependencies": { | ||
"kolorist": "^1.8.0", | ||
"meow": "^13.2.0", | ||
"prompts": "^2.4.2" | ||
}, | ||
"devDependencies": { | ||
"@types/meow": "^6.0.0", | ||
"@types/prompts": "^2.4.9", | ||
"microbundle": "^0.15.1" | ||
} | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,23 +1,271 @@ | ||
import prompts from 'prompts'; | ||
|
||
const config = await prompts([ | ||
{ | ||
name: 'title', | ||
type: 'text', | ||
message: 'Title for the application', | ||
validate: (text) => text.length === 0 ? 'Title is required' : true | ||
}, | ||
{ | ||
name: 'accessToken', | ||
type: 'text', | ||
message: 'Access token for CARTO API', | ||
validate: (text) => text.length === 0 ? 'Access token is required' : true | ||
}, | ||
{ | ||
name: 'apiBaseUrl', | ||
type: 'text', | ||
message: 'Base URL for CARTO API (optional)', | ||
} | ||
]) | ||
|
||
console.log(config) | ||
import { | ||
copyFileSync, | ||
existsSync, | ||
mkdirSync, | ||
readdirSync, | ||
rmSync, | ||
statSync, | ||
} from "node:fs"; | ||
import { | ||
rm, | ||
stat, | ||
copyFile, | ||
readFile, | ||
writeFile, | ||
mkdir, | ||
} from "node:fs/promises"; | ||
import { resolve } from "node:path"; | ||
import prompts from "prompts"; | ||
import { green, bold, dim, bgGray, bgGreen, yellow } from "kolorist"; | ||
|
||
// TODO: Simplify this. | ||
const pkg = pkgFromUserAgent(process.env.npm_config_user_agent); | ||
const pkgManager = pkg ? pkg.name : "npm"; | ||
const isYarn1 = pkgManager === "yarn" && pkg?.version.startsWith("1."); | ||
|
||
/** List of relative paths in the template to be _removed_ from new projects. */ | ||
const TEMPLATE_EXCLUDE_PATHS = ["node_modules", "scripts"]; | ||
|
||
/** List of dependencies in the template to be _removed_ from new projects. */ | ||
const TEMPLATE_EXCLUDE_DEPS = ["@carto/create-common"]; | ||
|
||
/** | ||
* List of package.json fields to clear from new projects. | ||
* See: https://docs.npmjs.com/cli/v10/configuring-npm/package-json | ||
*/ | ||
const TEMPLATE_EXCLUDE_PKG_FIELDS = [ | ||
"author", | ||
"bin", | ||
"bugs", | ||
"description", | ||
"files", | ||
"homepage", | ||
"keywords", | ||
"license", | ||
"publishConfig", | ||
"repository", | ||
"version", | ||
]; | ||
|
||
/** | ||
* Creates a new CARTO app in the target directory, given a template. | ||
* | ||
* @param templateDir Absolute path to template directory | ||
* @param targetDir Relative or absolute path to target directory | ||
*/ | ||
export async function createProject( | ||
templateDir: string, | ||
inputTargetDir: string, | ||
) { | ||
const targetDir = resolve(process.cwd(), inputTargetDir); | ||
|
||
console.log(` | ||
${green("✔")} ${bold("Template directory")} ${dim("…")} ${templateDir} | ||
${green("✔")} ${bold("Target directory")} ${dim("…")} ${targetDir} | ||
`); | ||
|
||
console.log(dim("…\n")); | ||
|
||
/**************************************************************************** | ||
* Validate target directory. | ||
*/ | ||
|
||
if (targetDir === templateDir) { | ||
throw new Error(`Target and template directories cannot be the same.`); | ||
} | ||
|
||
if (existsSync(targetDir) && !isEmpty(targetDir)) { | ||
const { overwrite } = await prompts([ | ||
{ | ||
type: "confirm", | ||
name: "overwrite", | ||
message: `Target directory "${targetDir}" is not empty. Overwrite?`, | ||
}, | ||
]); | ||
|
||
if (!overwrite) { | ||
console.warn(`Project creation cancelled.`); | ||
process.exit(2); | ||
} | ||
} else if (!existsSync(targetDir)) { | ||
mkdirSync(targetDir, { recursive: true }); | ||
} | ||
|
||
/**************************************************************************** | ||
* Project configuration. | ||
*/ | ||
|
||
const config = await prompts( | ||
[ | ||
{ | ||
name: "title", | ||
type: "text", | ||
message: "Title for the application", | ||
validate: (text) => (text.length === 0 ? "Title is required" : true), | ||
}, | ||
{ | ||
name: "accessToken", | ||
type: "password", | ||
message: "Access token for CARTO API", | ||
validate: (text) => | ||
text.length === 0 ? "Access token is required" : true, | ||
}, | ||
{ | ||
name: "apiBaseUrl", | ||
type: "text", | ||
message: "Base URL for CARTO API (optional)", | ||
}, | ||
], | ||
{ | ||
onCancel: () => { | ||
console.warn(`Project creation cancelled.`); | ||
process.exit(2); | ||
}, | ||
}, | ||
); | ||
|
||
console.log(dim("\n…")); | ||
|
||
/**************************************************************************** | ||
* Populate project directory. | ||
*/ | ||
|
||
// Overwrite was explicitly approved by user above. | ||
if (existsSync(targetDir) && !isEmpty(targetDir)) { | ||
emptyDir(targetDir); | ||
} | ||
|
||
copyDir(templateDir, targetDir); | ||
|
||
// Remove template files not needed in project. | ||
for (const excludePath of TEMPLATE_EXCLUDE_PATHS) { | ||
await rm(resolve(targetDir, excludePath), { recursive: true, force: true }); | ||
} | ||
|
||
// Set up package.json. | ||
const pkgPath = resolve(targetDir, "package.json"); | ||
const pkg = JSON.parse(await readFile(pkgPath, "utf8")); | ||
removePkgDependencies(pkg, TEMPLATE_EXCLUDE_DEPS); | ||
removePkgFields(pkg, TEMPLATE_EXCLUDE_PKG_FIELDS); | ||
pkg.name = toValidPackageName(config.title); | ||
pkg.private = true; | ||
await writeFile(pkgPath, JSON.stringify(pkg, null, 2)); | ||
|
||
// Suggest next steps | ||
console.log(` | ||
${green("✔")} ${bold(`Project "${config.title}" was created!`)} | ||
${bold(yellow("!"))} ${bold("Next steps")}: | ||
${[ | ||
...(inputTargetDir !== "." ? [`${dim("$")} cd ${inputTargetDir}`] : []), | ||
`${dim("$")} yarn`, | ||
`${dim("$")} yarn dev`, | ||
].join("\n")} | ||
`); | ||
} | ||
|
||
/****************************************************************************** | ||
* Utility functions. | ||
* | ||
* References: | ||
* - https://github.com/vitejs/vite/blob/main/packages/create-vite/src/index.ts | ||
*/ | ||
|
||
function removePkgDependencies<T extends Record<string, unknown>>( | ||
pkg: T, | ||
excludeDeps: string[], | ||
): T { | ||
const dependencyTypes = [ | ||
"dependencies", | ||
"devDependencies", | ||
"optionalDependencies", | ||
"peerDependencies", | ||
]; | ||
|
||
for (const exclude of excludeDeps) { | ||
for (const type of dependencyTypes) { | ||
const dependencies = pkg[type] as Record<string, string> | undefined; | ||
if (dependencies && dependencies[exclude]) { | ||
delete dependencies[exclude]; | ||
} | ||
} | ||
} | ||
|
||
return pkg; | ||
} | ||
|
||
function removePkgFields<T extends Record<string, unknown>>( | ||
pkg: T, | ||
excludeFields: string[], | ||
): T { | ||
for (const field of TEMPLATE_EXCLUDE_PKG_FIELDS) { | ||
delete pkg[field]; | ||
} | ||
return pkg; | ||
} | ||
|
||
function formatTargetDir(targetDir: string | undefined) { | ||
return targetDir?.trim().replace(/\/+$/g, ""); | ||
} | ||
|
||
function copy(src: string, dest: string) { | ||
const stat = statSync(src); | ||
if (stat.isDirectory()) { | ||
copyDir(src, dest); | ||
} else { | ||
copyFileSync(src, dest); | ||
} | ||
} | ||
|
||
const VALIDATE_PKG_NAME_REGEX = | ||
/^(?:@[a-z\d\-*~][a-z\d\-*._~]*\/)?[a-z\d\-~][a-z\d\-._~]*$/; | ||
|
||
function toValidPackageName(projectName: string) { | ||
if (VALIDATE_PKG_NAME_REGEX.test(projectName)) { | ||
return projectName; | ||
} | ||
|
||
return projectName | ||
.trim() | ||
.toLowerCase() | ||
.replace(/\s+/g, "-") | ||
.replace(/^[._]/, "") | ||
.replace(/[^a-z\d\-~]+/g, "-"); | ||
} | ||
|
||
function copyDir(srcDir: string, destDir: string) { | ||
mkdirSync(destDir, { recursive: true }); | ||
for (const file of readdirSync(srcDir)) { | ||
const srcFile = resolve(srcDir, file); | ||
const destFile = resolve(destDir, file); | ||
copy(srcFile, destFile); | ||
} | ||
} | ||
|
||
function isEmpty(path: string) { | ||
const files = readdirSync(path); | ||
return files.length === 0 || (files.length === 1 && files[0] === ".git"); | ||
} | ||
|
||
function emptyDir(dir: string) { | ||
if (!existsSync(dir)) { | ||
return; | ||
} | ||
for (const file of readdirSync(dir)) { | ||
if (file === ".git") { | ||
continue; | ||
} | ||
rmSync(resolve(dir, file), { recursive: true, force: true }); | ||
} | ||
} | ||
|
||
function pkgFromUserAgent(userAgent: string | undefined) { | ||
if (!userAgent) return undefined; | ||
const pkgSpec = userAgent.split(" ")[0]; | ||
const pkgSpecArr = pkgSpec.split("/"); | ||
return { | ||
name: pkgSpecArr[0], | ||
version: pkgSpecArr[1], | ||
}; | ||
} |
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
File renamed without changes
Oops, something went wrong.