diff --git a/README.md b/README.md index 910d9ec..7dc9ca0 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,14 @@ -**⚠️ This project is under development and is not ready for public use.** +> This project is in early access and requires an invite code to use. +> +> ⤷ [Sign up for early access](https://phuctm97.gumroad.com/l/nbundle-waitlist) -All `1.0.x` releases are considered `alpha` releases, are not stable, and may have breaking changes. +**⚠️ All `1.0.x` releases are `alpha` releases, are not stable, and may have breaking changes.** --- # Create Notion App · [![npm Version](https://img.shields.io/npm/v/create-notion-app?logo=npm)](https://www.npmjs.com/package/create-notion-app) [![CI](https://github.com/nbundle/create-notion-app/actions/workflows/ci.yml/badge.svg)](https://github.com/nbundle/create-notion-app/actions/workflows/ci.yml) -Create [nbundle]-powered [Notion] apps with one command: +Create [nbundle-powered][nbundle] [Notion] apps with one command: ```shell yarn create notion-app @@ -32,5 +34,5 @@ See [CONTRIBUTING.md](CONTRIBUTING.md). -[nbundle]: https://www.nbundle.com +[nbundle]: https://developers.nbundle.com [notion]: https://www.notion.so diff --git a/package.json b/package.json index e1e3483..4478400 100644 --- a/package.json +++ b/package.json @@ -51,10 +51,12 @@ "@types/ms": "^0.7.31", "@types/nanoid-dictionary": "^4.2.0", "@types/node": "^14.18.12", + "@types/prompts": "^2.0.14", "@types/tar": "^6.1.1", "async-retry": "^1.3.3", "capital-case": "^1.0.4", "cspell": "^6.1.2", + "email-validator": "^2.0.4", "got": "^12.1.0", "husky": "^8.0.1", "lint-staged": "^13.0.1", @@ -62,7 +64,9 @@ "nanoid": "^4.0.0", "nanoid-dictionary": "^4.3.0", "node-fetch": "^3.2.6", + "open": "^8.4.0", "prettier": "^2.6.2", + "prompts": "^2.4.2", "semantic-release": "^19.0.3", "tar": "^6.1.11", "ts-loader": "^9.3.0", diff --git a/src/index.ts b/src/index.ts index c7abcff..69ed77c 100644 --- a/src/index.ts +++ b/src/index.ts @@ -2,8 +2,10 @@ import path from "path"; import fs from "fs/promises"; import chalk from "chalk"; import updateCheck from "update-check"; +import prompts from "prompts"; import { Command, Option } from "commander"; +import requireInviteCode from "./require-invite-code"; import create from "./create"; async function getPkg() { @@ -33,16 +35,16 @@ async function checkForUpdates(): Promise { } async function run() { - const program = new Command(); + await requireInviteCode(); + const program = new Command(); const pkg = await getPkg(); - program .name(pkg.name) .description(pkg.description) .version(pkg.version, "-v, -V, --version") - .arguments("") - .usage(` [options]`) + .arguments("[]") + .usage(`[] [options]`) .option("-t, --ts, --typescript", "initialize as a TypeScript project") .addOption( new Option( @@ -50,12 +52,24 @@ async function run() { "initialize as a TypeScript project" ).hideHelp() ) - .action((projectDirectory, opts) => - create(path.resolve(projectDirectory), { + .action(async (optionalProjectDirectory, opts) => { + let projectDirectory = optionalProjectDirectory; + if (!projectDirectory) { + const { projectName } = await prompts({ + name: "projectName", + type: "text", + message: "What is your project name?", + }); + if (!projectName) { + console.error(chalk.red("Project name is required.")); + process.exit(1); + } + projectDirectory = projectName; + } + await create(path.resolve(projectDirectory), { typescript: opts.ts || opts.typescript, - }) - ); - + }); + }); await program.parseAsync(); } diff --git a/src/require-invite-code.ts b/src/require-invite-code.ts new file mode 100644 index 0000000..0865754 --- /dev/null +++ b/src/require-invite-code.ts @@ -0,0 +1,125 @@ +import prompts from "prompts"; +import chalk from "chalk"; +import fetch from "node-fetch"; +import open from "open"; +import emailValidator from "email-validator"; + +export default async function requireInviteCode(): Promise { + const { whatToDo } = await prompts({ + name: "whatToDo", + type: "select", + message: + "nbundle for Developers is in early access and requires an invite code.", + choices: [ + { + title: "I don't have any invite codes yet, sign me up", + value: "no-code", + }, + { + title: "I have an invite code, continue", + value: "has-code", + }, + ], + initial: 0, + }); + if (!whatToDo) { + console.error( + chalk.red( + "Invite code is required. You can sign up for early access at https://developers.nbundle.com/early-access." + ) + ); + process.exit(1); + } + + if (whatToDo === "no-code") { + await open("https://developers.nbundle.com/early-access"); + console.log( + "Open https://developers.nbundle.com/early-access to sign up for early access." + ); + process.exit(0); + } + + const { code } = await prompts({ + name: "code", + type: "text", + message: "What is your invite code?", + validate: (value) => { + const trimmedValue = value.trim(); + if (trimmedValue.length === 0) return "Please enter an invite code."; + return true; + }, + }); + if (!code) { + console.error( + chalk.red( + "Invite code is required. You can sign up for early access at https://developers.nbundle.com/early-access." + ) + ); + process.exit(1); + } + + const preVerify = await fetch( + "https://developers.nbundle.com/api/v1/invite-verifications", + { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ code }), + } + ); + if (!preVerify.ok) { + const json = (await preVerify.json()) as { message: string }; + console.error( + chalk.red( + `${ + preVerify.status === 404 ? "Invite code is invalid." : json.message + } Please try again or sign up for early access at https://developers.nbundle.com/early-access if you haven't already.` + ) + ); + process.exit(1); + } + const preVerifyJson = (await preVerify.json()) as { email: string }; + + const { email } = await prompts({ + name: "email", + type: "text", + message: `What is your email address that got this invite code? (${preVerifyJson.email})`, + validate: async (value): Promise => { + const trimmedValue = value.trim(); + if (trimmedValue.length === 0) return "Email is required."; + if (!emailValidator.validate(trimmedValue)) return "Email is invalid."; + return true; + }, + }); + if (!email) { + console.error( + chalk.red( + "Email is required to verify if you're the owner of the invite code. Please try again or sign up for early access at https://developers.nbundle.com/early-access if you haven't already." + ) + ); + process.exit(1); + } + + const verify = await fetch( + "https://developers.nbundle.com/api/v1/invite-verifications", + { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ code, email }), + } + ); + if (!verify.ok) { + const json = (await verify.json()) as { message: string }; + console.error( + chalk.red( + `${ + verify.status === 404 ? "Invite code is invalid." : json.message + } Please try again or sign up for early access at https://developers.nbundle.com/early-access if you haven't already.` + ) + ); + process.exit(1); + } +} diff --git a/yarn.lock b/yarn.lock index c26ed75..0eff54d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1090,6 +1090,13 @@ resolved "https://registry.yarnpkg.com/@types/parse-json/-/parse-json-4.0.0.tgz#2f8bb441434d163b35fb8ffdccd7138927ffb8c0" integrity sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA== +"@types/prompts@^2.0.14": + version "2.0.14" + resolved "https://registry.yarnpkg.com/@types/prompts/-/prompts-2.0.14.tgz#10cb8899844bb0771cabe57c1becaaaca9a3b521" + integrity sha512-HZBd99fKxRWpYCErtm2/yxUZv6/PBI9J7N4TNFffl5JbrYMHBwF25DjQGTW3b3jmXq+9P6/8fCIb2ee57BFfYA== + dependencies: + "@types/node" "*" + "@types/responselike@*", "@types/responselike@^1.0.0": version "1.0.0" resolved "https://registry.yarnpkg.com/@types/responselike/-/responselike-1.0.0.tgz#251f4fe7d154d2bad125abe1b429b23afd262e29" @@ -2163,6 +2170,11 @@ defer-to-connect@^2.0.1: resolved "https://registry.yarnpkg.com/defer-to-connect/-/defer-to-connect-2.0.1.tgz#8016bdb4143e4632b77a3449c6236277de520587" integrity sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg== +define-lazy-prop@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz#3f7ae421129bcaaac9bc74905c98a0009ec9ee7f" + integrity sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og== + del@^6.0.0: version "6.1.1" resolved "https://registry.yarnpkg.com/del/-/del-6.1.1.tgz#3b70314f1ec0aa325c6b14eb36b95786671edb7a" @@ -2246,6 +2258,11 @@ electron-to-chromium@^1.4.147: resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.152.tgz#7dedbe8f3dc1c597088982a203f392e60f7ee90a" integrity sha512-jk4Ju5SGZAQQJ1iI4Rgru7dDlvkQPLpNPWH9gIZmwCD4YteA5Bbk1xPcPDUf5jUYs3e1e80RXdi8XgKQZaigeg== +email-validator@^2.0.4: + version "2.0.4" + resolved "https://registry.yarnpkg.com/email-validator/-/email-validator-2.0.4.tgz#b8dfaa5d0dae28f1b03c95881d904d4e40bfe7ed" + integrity sha512-gYCwo7kh5S3IDyZPLZf6hSS0MnZT8QmJFqYvbqlDZSbwdZlY6QZWxJ4i/6UhITOJ4XzyI647Bm2MXKCLqnJ4nQ== + emoji-regex@^8.0.0: version "8.0.0" resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37" @@ -3014,6 +3031,11 @@ is-core-module@^2.5.0, is-core-module@^2.8.1: dependencies: has "^1.0.3" +is-docker@^2.0.0, is-docker@^2.1.1: + version "2.2.1" + resolved "https://registry.yarnpkg.com/is-docker/-/is-docker-2.2.1.tgz#33eeabe23cfe86f14bde4408a02c0cfb853acdaa" + integrity sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ== + is-extglob@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" @@ -3105,6 +3127,13 @@ is-typedarray@^1.0.0: resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a" integrity sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA== +is-wsl@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/is-wsl/-/is-wsl-2.2.0.tgz#74a4c76e77ca9fd3f932f290c17ea326cd157271" + integrity sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww== + dependencies: + is-docker "^2.0.0" + isarray@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" @@ -3229,6 +3258,11 @@ kind-of@^6.0.2, kind-of@^6.0.3: resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-6.0.3.tgz#07c05034a6c349fa06e24fa35aa76db4580ce4dd" integrity sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw== +kleur@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/kleur/-/kleur-3.0.3.tgz#a79c9ecc86ee1ce3fa6206d1216c501f147fc07e" + integrity sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w== + libnpmaccess@^6.0.2: version "6.0.3" resolved "https://registry.yarnpkg.com/libnpmaccess/-/libnpmaccess-6.0.3.tgz#473cc3e4aadb2bc713419d92e45d23b070d8cded" @@ -4158,6 +4192,15 @@ onetime@^6.0.0: dependencies: mimic-fn "^4.0.0" +open@^8.4.0: + version "8.4.0" + resolved "https://registry.yarnpkg.com/open/-/open-8.4.0.tgz#345321ae18f8138f82565a910fdc6b39e8c244f8" + integrity sha512-XgFPPM+B28FtCCgSb9I+s9szOC1vZRSwgWsRUA5ylIxRTgKozqjOCrVOqGsYABPYK5qnfqClxZTFBa8PKt2v6Q== + dependencies: + define-lazy-prop "^2.0.0" + is-docker "^2.1.1" + is-wsl "^2.2.0" + opener@^1.5.2: version "1.5.2" resolved "https://registry.yarnpkg.com/opener/-/opener-1.5.2.tgz#5d37e1f35077b9dcac4301372271afdeb2a13598" @@ -4443,6 +4486,14 @@ promise-retry@^2.0.1: err-code "^2.0.2" retry "^0.12.0" +prompts@^2.4.2: + version "2.4.2" + resolved "https://registry.yarnpkg.com/prompts/-/prompts-2.4.2.tgz#7b57e73b3a48029ad10ebd44f74b01722a4cb069" + integrity sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q== + dependencies: + kleur "^3.0.3" + sisteransi "^1.0.5" + promzard@^0.3.0: version "0.3.0" resolved "https://registry.yarnpkg.com/promzard/-/promzard-0.3.0.tgz#26a5d6ee8c7dee4cb12208305acfb93ba382a9ee" @@ -4891,6 +4942,11 @@ signale@^1.2.1: figures "^2.0.0" pkg-conf "^2.1.0" +sisteransi@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/sisteransi/-/sisteransi-1.0.5.tgz#134d681297756437cc05ca01370d3a7a571075ed" + integrity sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg== + slash@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634"