|
|
@@ -1,3 +1,4 @@
|
|
|
+/* eslint-disable tsdoc/syntax */
|
|
|
// this rule is absolutely broken for the typings the prompts library
|
|
|
// provides, so we need to hard-disable it for all usages of prompts
|
|
|
|
|
|
@@ -21,10 +22,28 @@ import type {
|
|
|
} from "./types.js";
|
|
|
import { PACKAGE_PREFIX, PackageType, TEMPLATES_FOLDER } from "./types.js";
|
|
|
|
|
|
-function getPackageNameWithoutOrg(
|
|
|
- packageNameWithOrg: string | null | undefined,
|
|
|
-) {
|
|
|
- return packageNameWithOrg?.split("/")[1] ?? "";
|
|
|
+/**
|
|
|
+ * Given either a raw name ("foo") or a scoped name ("@pythnetwork/foo"),
|
|
|
+ * returns the normalized pair { raw, withOrg } where:
|
|
|
+ * - raw is the unscoped package name ("foo")
|
|
|
+ * - withOrg is the scoped package name ("@pythnetwork/foo")
|
|
|
+ */
|
|
|
+function normalizePackageNameInput(val: string | null | undefined = "") {
|
|
|
+ // if the user passed a scoped name already, extract the part after `/`
|
|
|
+ if (val?.startsWith("@")) {
|
|
|
+ const parts = val.split("/");
|
|
|
+ const raw = parts[1] ?? "";
|
|
|
+ return {
|
|
|
+ raw,
|
|
|
+ withOrg: `${PACKAGE_PREFIX}${raw}`,
|
|
|
+ };
|
|
|
+ }
|
|
|
+ // otherwise treat input as raw
|
|
|
+ const raw = val ?? "";
|
|
|
+ return {
|
|
|
+ raw,
|
|
|
+ withOrg: `${PACKAGE_PREFIX}${raw}`,
|
|
|
+ };
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
@@ -51,98 +70,99 @@ function getTemplatesInputFolder(packageType: PackageType) {
|
|
|
}
|
|
|
|
|
|
async function createPythApp() {
|
|
|
- const { confirm, description, folder, isPublic, packageName, packageType } =
|
|
|
- (await prompts([
|
|
|
- {
|
|
|
- choices: Object.values(PackageType).map((val) => ({
|
|
|
- title: val,
|
|
|
- value: val,
|
|
|
- })),
|
|
|
- message: "Which type of package do you want to create?",
|
|
|
- name: "packageType",
|
|
|
- type: "select",
|
|
|
- },
|
|
|
- {
|
|
|
- format: (val: string) => `${PACKAGE_PREFIX}${val}`,
|
|
|
- message: (_, responses: InProgressCreatePythAppResponses) =>
|
|
|
- `Enter the name for your ${responses.packageType ?? ""} package. ${chalk.magenta(PACKAGE_PREFIX)}`,
|
|
|
- name: "packageName",
|
|
|
- type: "text",
|
|
|
- validate: (name: string) => {
|
|
|
- const proposedName = `${PACKAGE_PREFIX}${name}`;
|
|
|
- const pjsonNameRegexp = /^@pythnetwork\/(\w)(\w|\d|_|-)+$/;
|
|
|
- return (
|
|
|
- pjsonNameRegexp.test(proposedName) ||
|
|
|
- "Please enter a valid package name (you do not need to add @pythnetwork/ as a prefix, it will be added automatically)"
|
|
|
- );
|
|
|
- },
|
|
|
- },
|
|
|
- {
|
|
|
- message: "Enter a brief, friendly description for your package",
|
|
|
- name: "description",
|
|
|
- type: "text",
|
|
|
+ const responses = (await prompts([
|
|
|
+ {
|
|
|
+ choices: Object.values(PackageType).map((val) => ({
|
|
|
+ title: val,
|
|
|
+ value: val,
|
|
|
+ })),
|
|
|
+ message: "Which type of package do you want to create?",
|
|
|
+ name: "packageType",
|
|
|
+ type: "select",
|
|
|
+ },
|
|
|
+ {
|
|
|
+ // Store the raw name (no format). We'll normalize after prompts
|
|
|
+ message: (_, responses: InProgressCreatePythAppResponses) =>
|
|
|
+ `Enter the name for your ${responses.packageType ?? ""} package. ${chalk.magenta(PACKAGE_PREFIX)}`,
|
|
|
+ name: "packageName",
|
|
|
+ type: "text",
|
|
|
+ validate: (name: string) => {
|
|
|
+ // validate using the full scoped candidate so we ensure the raw name is valid
|
|
|
+ const proposedName = `${PACKAGE_PREFIX}${name.replace(/^@.*\//, "")}`;
|
|
|
+ const pjsonNameRegexp = /^@pythnetwork\/(\w)(\w|\d|_|-)+$/;
|
|
|
+ return (
|
|
|
+ pjsonNameRegexp.test(proposedName) ||
|
|
|
+ "Please enter a valid package name (you do not need to add @pythnetwork/ as a prefix, it will be added automatically)"
|
|
|
+ );
|
|
|
},
|
|
|
- {
|
|
|
- choices: (_, { packageType }: InProgressCreatePythAppResponses) =>
|
|
|
- getAvailableFolders()
|
|
|
- .map((val) => ({
|
|
|
- title: val,
|
|
|
- value: val,
|
|
|
- }))
|
|
|
- .filter(
|
|
|
- ({ value: relPath }) =>
|
|
|
- packageType !== PackageType.WEBAPP &&
|
|
|
- !relPath.startsWith("apps"),
|
|
|
- ),
|
|
|
- message: "Where do you want your package to live?",
|
|
|
- name: "folder",
|
|
|
- type: (_, { packageType }: InProgressCreatePythAppResponses) =>
|
|
|
- packageType === PackageType.WEBAPP ? false : "select",
|
|
|
+ },
|
|
|
+ {
|
|
|
+ message: "Enter a brief, friendly description for your package",
|
|
|
+ name: "description",
|
|
|
+ type: "text",
|
|
|
+ },
|
|
|
+ {
|
|
|
+ choices: (_, { packageType }: InProgressCreatePythAppResponses) =>
|
|
|
+ getAvailableFolders()
|
|
|
+ .map((val) => ({
|
|
|
+ title: val,
|
|
|
+ value: val,
|
|
|
+ }))
|
|
|
+ .filter(
|
|
|
+ ({ value: relPath }) =>
|
|
|
+ packageType !== PackageType.WEBAPP && !relPath.startsWith("apps"),
|
|
|
+ ),
|
|
|
+ message: "Where do you want your package to live?",
|
|
|
+ name: "folder",
|
|
|
+ type: (_, { packageType }: InProgressCreatePythAppResponses) =>
|
|
|
+ packageType === PackageType.WEBAPP ? false : "select",
|
|
|
+ },
|
|
|
+ {
|
|
|
+ message:
|
|
|
+ "Are you intending on publishing this, publicly on NPM, for users outside of our org to use?",
|
|
|
+ name: "isPublic",
|
|
|
+ type: (_, { packageType }: InProgressCreatePythAppResponses) =>
|
|
|
+ packageType === PackageType.WEBAPP ? false : "confirm",
|
|
|
+ },
|
|
|
+ {
|
|
|
+ message: (
|
|
|
+ _,
|
|
|
+ { folder, packageName, packageType }: InProgressCreatePythAppResponses,
|
|
|
+ ) => {
|
|
|
+ // normalize for display
|
|
|
+ const { raw: pkgRaw, withOrg: pkgWithOrg } =
|
|
|
+ normalizePackageNameInput(packageName);
|
|
|
+
|
|
|
+ let msg = `Please confirm your choices:${os.EOL}`;
|
|
|
+ msg += `Creating a ${chalk.magenta(packageType)} package, named ${chalk.magenta(pkgWithOrg)}, in ${chalk.magenta(packageType === PackageType.WEBAPP ? "apps" : folder)}/${pkgRaw}.${os.EOL}`;
|
|
|
+ msg += "Look good?";
|
|
|
+
|
|
|
+ return msg;
|
|
|
},
|
|
|
- {
|
|
|
- message:
|
|
|
- "Are you intending on publishing this, publicly on NPM, for users outside of our org to use?",
|
|
|
- name: "isPublic",
|
|
|
- type: "confirm",
|
|
|
- },
|
|
|
- {
|
|
|
- message: (
|
|
|
- _,
|
|
|
- {
|
|
|
- folder,
|
|
|
- packageName,
|
|
|
- packageType,
|
|
|
- }: InProgressCreatePythAppResponses,
|
|
|
- ) => {
|
|
|
- let msg = `Please confirm your choices:${os.EOL}`;
|
|
|
- msg += `Creating a ${chalk.magenta(packageType)} package, named ${chalk.magenta(packageName)}, in ${chalk.magenta(packageType === PackageType.WEBAPP ? "apps" : folder)}/${getPackageNameWithoutOrg(packageName)}.${os.EOL}`;
|
|
|
- msg += "Look good?";
|
|
|
-
|
|
|
- return msg;
|
|
|
- },
|
|
|
- name: "confirm",
|
|
|
- type: "confirm",
|
|
|
- },
|
|
|
- ])) as CreatePythAppResponses;
|
|
|
+ name: "confirm",
|
|
|
+ type: "confirm",
|
|
|
+ },
|
|
|
+ ])) as CreatePythAppResponses;
|
|
|
+
|
|
|
+ const { confirm, description, folder, isPublic, packageName, packageType } =
|
|
|
+ responses;
|
|
|
|
|
|
if (!confirm) {
|
|
|
Logger.warn("oops, you did not confirm your choices.");
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
- const packageNameWithoutOrg = getPackageNameWithoutOrg(packageName);
|
|
|
+ // normalize package-name inputs to deterministic values
|
|
|
+ const { raw: packageNameWithoutOrg, withOrg: packageNameWithOrg } =
|
|
|
+ normalizePackageNameInput(packageName);
|
|
|
|
|
|
const relDest =
|
|
|
packageType === PackageType.WEBAPP
|
|
|
? path.join("apps", packageNameWithoutOrg)
|
|
|
- : folder;
|
|
|
- const absDest = path.join(
|
|
|
- appRootPath.toString(),
|
|
|
- relDest,
|
|
|
- packageNameWithoutOrg,
|
|
|
- );
|
|
|
+ : path.join(folder, packageNameWithoutOrg);
|
|
|
+ const absDest = path.join(appRootPath.toString(), relDest);
|
|
|
|
|
|
- Logger.info("ensuring", relDest, "exists");
|
|
|
+ Logger.info("ensuring", relDest, `exists (abs path: ${absDest})`);
|
|
|
await fs.ensureDir(absDest);
|
|
|
|
|
|
Logger.info("copying files");
|
|
|
@@ -159,34 +179,43 @@ async function createPythApp() {
|
|
|
"updating files with the choices you made in the initial prompts",
|
|
|
);
|
|
|
await Promise.all(
|
|
|
- destFiles.filter(fp => !fp.includes('node_module')).map(async (fp) => {
|
|
|
- const contents = await fs.readFile(fp, "utf8");
|
|
|
- const updatedContents = renderTemplate(contents, {
|
|
|
- description,
|
|
|
- name: packageName,
|
|
|
- packageNameWithoutOrg,
|
|
|
- relativeFolder: relDest,
|
|
|
- });
|
|
|
- await fs.writeFile(fp, updatedContents, "utf8");
|
|
|
-
|
|
|
- if (fp.endsWith("package.json")) {
|
|
|
- const pjson = JSON.parse(updatedContents) as PackageJson;
|
|
|
- pjson.private = !isPublic;
|
|
|
- if (isPublic) {
|
|
|
- pjson.publishConfig = {
|
|
|
- access: "public",
|
|
|
- };
|
|
|
+ destFiles
|
|
|
+ .filter((fp) => !fp.includes("node_module"))
|
|
|
+ .map(async (fp) => {
|
|
|
+ const contents = await fs.readFile(fp, "utf8");
|
|
|
+ const updatedContents = renderTemplate(contents, {
|
|
|
+ description,
|
|
|
+ name: packageNameWithOrg,
|
|
|
+ packageNameWithoutOrg,
|
|
|
+ relativeFolder: relDest,
|
|
|
+ });
|
|
|
+ await fs.writeFile(fp, updatedContents, "utf8");
|
|
|
+
|
|
|
+ if (fp.endsWith("package.json")) {
|
|
|
+ const pjson = JSON.parse(updatedContents) as PackageJson;
|
|
|
+ // ensure package name in package.json is the scoped name
|
|
|
+ pjson.name = packageNameWithOrg;
|
|
|
+ pjson.private = !isPublic;
|
|
|
+ if (isPublic) {
|
|
|
+ pjson.publishConfig = {
|
|
|
+ access: "public",
|
|
|
+ };
|
|
|
+ } else {
|
|
|
+ // ensure publishConfig is removed if present and not public
|
|
|
+ if (pjson.publishConfig) {
|
|
|
+ delete pjson.publishConfig;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ await fs.writeFile(fp, JSON.stringify(pjson, undefined, 2), "utf8");
|
|
|
}
|
|
|
-
|
|
|
- await fs.writeFile(fp, JSON.stringify(pjson, undefined, 2), "utf8");
|
|
|
- }
|
|
|
- }),
|
|
|
+ }),
|
|
|
);
|
|
|
|
|
|
Logger.info("installing deps");
|
|
|
execSync("pnpm i", { cwd: appRootPath.toString(), stdio: "inherit" });
|
|
|
|
|
|
- Logger.info(`Done! ${packageName} is ready for development`);
|
|
|
+ Logger.info(`Done! ${packageNameWithOrg} is ready for development`);
|
|
|
}
|
|
|
|
|
|
await createPythApp();
|