mkcert.mjs 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629
  1. var __defProp = Object.defineProperty;
  2. var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
  3. var __publicField = (obj, key, value) => {
  4. __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
  5. return value;
  6. };
  7. // plugin/index.ts
  8. import { createLogger } from "vite";
  9. // plugin/lib/constant.ts
  10. import os from "os";
  11. import path from "path";
  12. var PKG_NAME = "vite-plugin-mkcert";
  13. var PLUGIN_NAME = PKG_NAME.replace(/-/g, ":");
  14. var PLUGIN_DATA_DIR = path.join(os.homedir(), `.${PKG_NAME}`);
  15. // plugin/lib/util.ts
  16. import child_process from "child_process";
  17. import crypto from "crypto";
  18. import fs from "fs";
  19. import os2 from "os";
  20. import path2 from "path";
  21. import util from "util";
  22. var exists = async (filePath) => {
  23. try {
  24. await fs.promises.access(filePath);
  25. return true;
  26. } catch (error) {
  27. return false;
  28. }
  29. };
  30. var resolvePath = (fileName) => {
  31. return path2.resolve(PLUGIN_DATA_DIR, fileName);
  32. };
  33. var mkdir = async (dirname) => {
  34. const isExist = await exists(dirname);
  35. if (!isExist) {
  36. await fs.promises.mkdir(dirname, { recursive: true });
  37. }
  38. };
  39. var ensureDirExist = async (filePath) => {
  40. const dirname = path2.dirname(filePath);
  41. await mkdir(dirname);
  42. };
  43. var readFile = async (filePath) => {
  44. const isExist = await exists(filePath);
  45. return isExist ? (await fs.promises.readFile(filePath)).toString() : void 0;
  46. };
  47. var writeFile = async (filePath, data) => {
  48. await ensureDirExist(filePath);
  49. await fs.promises.writeFile(filePath, data);
  50. await fs.promises.chmod(filePath, 511);
  51. };
  52. var exec = async (cmd, options) => {
  53. return await util.promisify(child_process.exec)(cmd, options);
  54. };
  55. var isIPV4 = (family) => {
  56. return family === "IPv4" || family === 4;
  57. };
  58. var getLocalV4Ips = () => {
  59. const interfaceDict = os2.networkInterfaces();
  60. const addresses = [];
  61. for (const key in interfaceDict) {
  62. const interfaces = interfaceDict[key];
  63. if (interfaces) {
  64. for (const item of interfaces) {
  65. if (isIPV4(item.family)) {
  66. addresses.push(item.address);
  67. }
  68. }
  69. }
  70. }
  71. return addresses;
  72. };
  73. var getDefaultHosts = () => {
  74. return ["localhost", ...getLocalV4Ips()];
  75. };
  76. var getHash = async (filePath) => {
  77. const content = await readFile(filePath);
  78. if (content) {
  79. const hash = crypto.createHash("sha256");
  80. hash.update(content);
  81. return hash.digest("hex");
  82. }
  83. return void 0;
  84. };
  85. var isObj = (obj) => Object.prototype.toString.call(obj) === "[object Object]";
  86. var mergeObj = (target, source) => {
  87. if (!(isObj(target) && isObj(source))) {
  88. return target;
  89. }
  90. for (const key in source) {
  91. if (Object.prototype.hasOwnProperty.call(source, key)) {
  92. const targetValue = target[key];
  93. const sourceValue = source[key];
  94. if (isObj(targetValue) && isObj(sourceValue)) {
  95. mergeObj(targetValue, sourceValue);
  96. } else {
  97. target[key] = sourceValue;
  98. }
  99. }
  100. }
  101. };
  102. var deepMerge = (target, ...source) => {
  103. return source.reduce((a, b) => mergeObj(a, b), target);
  104. };
  105. var prettyLog = (obj) => {
  106. return JSON.stringify(obj, null, 2);
  107. };
  108. var escape = (path3) => {
  109. return `"${path3}"`;
  110. };
  111. // plugin/mkcert/index.ts
  112. import fs2 from "fs";
  113. import process2 from "process";
  114. import pc from "picocolors";
  115. // plugin/lib/logger.ts
  116. import Debug from "debug";
  117. var debug = Debug(PLUGIN_NAME);
  118. // plugin/mkcert/config.ts
  119. var CONFIG_FILE_NAME = "config.json";
  120. var CONFIG_FILE_PATH = resolvePath(CONFIG_FILE_NAME);
  121. var Config = class {
  122. version;
  123. record;
  124. async init() {
  125. const str = await readFile(CONFIG_FILE_PATH);
  126. const options = str ? JSON.parse(str) : void 0;
  127. if (options) {
  128. this.version = options.version;
  129. this.record = options.record;
  130. }
  131. }
  132. async serialize() {
  133. await writeFile(CONFIG_FILE_PATH, prettyLog(this));
  134. }
  135. async merge(obj) {
  136. const currentStr = prettyLog(this);
  137. deepMerge(this, obj);
  138. const nextStr = prettyLog(this);
  139. debug(
  140. `Receive parameter ${prettyLog(
  141. obj
  142. )}, then update config from ${currentStr} to ${nextStr}`
  143. );
  144. await this.serialize();
  145. }
  146. getRecord() {
  147. return this.record;
  148. }
  149. getVersion() {
  150. return this.version;
  151. }
  152. };
  153. var config_default = Config;
  154. // plugin/lib/request.ts
  155. import axios from "axios";
  156. var request = axios.create();
  157. request.interceptors.response.use(
  158. (res) => {
  159. return res;
  160. },
  161. (error) => {
  162. debug("Request error: %o", error);
  163. return Promise.reject(error);
  164. }
  165. );
  166. var request_default = request;
  167. // plugin/mkcert/downloader.ts
  168. var Downloader = class {
  169. static create() {
  170. return new Downloader();
  171. }
  172. constructor() {
  173. }
  174. async download(downloadUrl, savedPath) {
  175. debug("Downloading the mkcert executable from %s", downloadUrl);
  176. const { data } = await request_default.get(downloadUrl, {
  177. responseType: "arraybuffer"
  178. });
  179. await writeFile(savedPath, data);
  180. debug("The mkcert has been saved to %s", savedPath);
  181. }
  182. };
  183. var downloader_default = Downloader;
  184. // plugin/mkcert/record.ts
  185. var Record = class {
  186. config;
  187. constructor(options) {
  188. this.config = options.config;
  189. }
  190. getHosts() {
  191. return this.config.getRecord()?.hosts;
  192. }
  193. getHash() {
  194. return this.config.getRecord()?.hash;
  195. }
  196. contains(hosts) {
  197. const oldHosts = this.getHosts();
  198. if (!oldHosts) {
  199. return false;
  200. }
  201. for (const host of hosts) {
  202. if (!oldHosts.includes(host)) {
  203. return false;
  204. }
  205. }
  206. return true;
  207. }
  208. tamper(hash) {
  209. const oldHash = this.getHash();
  210. if (!oldHash) {
  211. return false;
  212. }
  213. if (oldHash.key === hash.key && oldHash.cert === hash.cert) {
  214. return false;
  215. }
  216. return true;
  217. }
  218. async update(record) {
  219. await this.config.merge({ record });
  220. }
  221. };
  222. var record_default = Record;
  223. // plugin/mkcert/source.ts
  224. import { Octokit } from "@octokit/rest";
  225. var BaseSource = class {
  226. getPlatformIdentifier() {
  227. switch (process.platform) {
  228. case "win32":
  229. return "windows-amd64.exe";
  230. case "linux":
  231. return process.arch === "arm64" ? "linux-arm64" : process.arch === "arm" ? "linux-arm" : "linux-amd64";
  232. case "darwin":
  233. return "darwin-amd64";
  234. default:
  235. throw new Error("Unsupported platform");
  236. }
  237. }
  238. };
  239. var GithubSource = class extends BaseSource {
  240. static create() {
  241. return new GithubSource();
  242. }
  243. constructor() {
  244. super();
  245. }
  246. async getSourceInfo() {
  247. const octokit = new Octokit();
  248. const { data } = await octokit.repos.getLatestRelease({
  249. owner: "FiloSottile",
  250. repo: "mkcert"
  251. });
  252. const platformIdentifier = this.getPlatformIdentifier();
  253. const version = data.tag_name;
  254. const downloadUrl = data.assets.find(
  255. (item) => item.name.includes(platformIdentifier)
  256. )?.browser_download_url;
  257. if (!(version && downloadUrl)) {
  258. return void 0;
  259. }
  260. return {
  261. downloadUrl,
  262. version
  263. };
  264. }
  265. };
  266. var _CodingSource = class extends BaseSource {
  267. static create() {
  268. return new _CodingSource();
  269. }
  270. constructor() {
  271. super();
  272. }
  273. async request(data) {
  274. return request_default({
  275. data,
  276. method: "POST",
  277. url: _CodingSource.CODING_API,
  278. headers: {
  279. Authorization: _CodingSource.CODING_AUTHORIZATION
  280. }
  281. });
  282. }
  283. getPackageName() {
  284. return `mkcert-${this.getPlatformIdentifier()}`;
  285. }
  286. async getSourceInfo() {
  287. const { data: VersionData } = await this.request({
  288. Action: "DescribeArtifactVersionList",
  289. ProjectId: _CodingSource.CODING_PROJECT_ID,
  290. Repository: _CodingSource.REPOSITORY,
  291. Package: this.getPackageName(),
  292. PageSize: 1
  293. });
  294. const version = VersionData.Response.Data.InstanceSet[0]?.Version;
  295. if (!version) {
  296. return void 0;
  297. }
  298. const { data: FileData } = await this.request({
  299. Action: "DescribeArtifactFileDownloadUrl",
  300. ProjectId: _CodingSource.CODING_PROJECT_ID,
  301. Repository: _CodingSource.REPOSITORY,
  302. Package: this.getPackageName(),
  303. PackageVersion: version
  304. });
  305. const downloadUrl = FileData.Response.Url;
  306. if (!downloadUrl) {
  307. return void 0;
  308. }
  309. return {
  310. downloadUrl,
  311. version
  312. };
  313. }
  314. };
  315. var CodingSource = _CodingSource;
  316. __publicField(CodingSource, "CODING_API", "https://e.coding.net/open-api");
  317. __publicField(CodingSource, "CODING_AUTHORIZATION", "token 000f7831ec425079439b0f55f55c729c9280d66e");
  318. __publicField(CodingSource, "CODING_PROJECT_ID", 8524617);
  319. __publicField(CodingSource, "REPOSITORY", "mkcert");
  320. // plugin/mkcert/version.ts
  321. var parseVersion = (version) => {
  322. const str = version.trim().replace(/v/i, "");
  323. return str.split(".");
  324. };
  325. var VersionManger = class {
  326. config;
  327. constructor(props) {
  328. this.config = props.config;
  329. }
  330. async update(version) {
  331. try {
  332. await this.config.merge({ version });
  333. } catch (err) {
  334. debug("Failed to record mkcert version info: %o", err);
  335. }
  336. }
  337. compare(version) {
  338. const currentVersion = this.config.getVersion();
  339. if (!currentVersion) {
  340. return {
  341. currentVersion,
  342. nextVersion: version,
  343. breakingChange: false,
  344. shouldUpdate: true
  345. };
  346. }
  347. let breakingChange = false;
  348. let shouldUpdate = false;
  349. const newVersion = parseVersion(version);
  350. const oldVersion = parseVersion(currentVersion);
  351. for (let i = 0; i < newVersion.length; i++) {
  352. if (newVersion[i] > oldVersion[i]) {
  353. shouldUpdate = true;
  354. breakingChange = i === 0;
  355. break;
  356. }
  357. }
  358. return {
  359. breakingChange,
  360. shouldUpdate,
  361. currentVersion,
  362. nextVersion: version
  363. };
  364. }
  365. };
  366. var version_default = VersionManger;
  367. // plugin/mkcert/index.ts
  368. var KEY_FILE_PATH = resolvePath("certs/dev.key");
  369. var CERT_FILE_PATH = resolvePath("certs/dev.pem");
  370. var Mkcert = class {
  371. force;
  372. autoUpgrade;
  373. mkcertLocalPath;
  374. source;
  375. logger;
  376. mkcertSavedPath;
  377. sourceType;
  378. config;
  379. static create(options) {
  380. return new Mkcert(options);
  381. }
  382. constructor(options) {
  383. const { force, autoUpgrade, source, mkcertPath, logger } = options;
  384. this.force = force;
  385. this.logger = logger;
  386. this.autoUpgrade = autoUpgrade;
  387. this.mkcertLocalPath = mkcertPath;
  388. this.sourceType = source || "github";
  389. if (this.sourceType === "github") {
  390. this.source = GithubSource.create();
  391. } else if (this.sourceType === "coding") {
  392. this.source = CodingSource.create();
  393. } else {
  394. this.source = this.sourceType;
  395. }
  396. this.mkcertSavedPath = resolvePath(
  397. process2.platform === "win32" ? "mkcert.exe" : "mkcert"
  398. );
  399. this.config = new config_default();
  400. }
  401. async getMkcertBinnary() {
  402. return await this.checkMkcert() ? this.mkcertLocalPath || this.mkcertSavedPath : void 0;
  403. }
  404. async checkMkcert() {
  405. let exist;
  406. if (this.mkcertLocalPath) {
  407. exist = await exists(this.mkcertLocalPath);
  408. if (!exists) {
  409. this.logger.error(
  410. pc.red(
  411. `${this.mkcertLocalPath} does not exist, please check the mkcertPath paramter`
  412. )
  413. );
  414. }
  415. } else {
  416. exist = await exists(this.mkcertSavedPath);
  417. }
  418. return exist;
  419. }
  420. async getCertificate() {
  421. const key = await fs2.promises.readFile(KEY_FILE_PATH);
  422. const cert = await fs2.promises.readFile(CERT_FILE_PATH);
  423. return {
  424. key,
  425. cert
  426. };
  427. }
  428. async createCertificate(hosts) {
  429. const names = hosts.join(" ");
  430. const mkcertBinnary = await this.getMkcertBinnary();
  431. if (!mkcertBinnary) {
  432. debug(
  433. `Mkcert does not exist, unable to generate certificate for ${names}`
  434. );
  435. }
  436. await ensureDirExist(KEY_FILE_PATH);
  437. await ensureDirExist(CERT_FILE_PATH);
  438. const cmd = `${escape(mkcertBinnary)} -install -key-file ${escape(
  439. KEY_FILE_PATH
  440. )} -cert-file ${escape(CERT_FILE_PATH)} ${names}`;
  441. await exec(cmd, {
  442. env: {
  443. ...process2.env,
  444. JAVA_HOME: void 0
  445. }
  446. });
  447. this.logger.info(
  448. `The certificate is saved in:
  449. ${KEY_FILE_PATH}
  450. ${CERT_FILE_PATH}`
  451. );
  452. }
  453. getLatestHash = async () => {
  454. return {
  455. key: await getHash(KEY_FILE_PATH),
  456. cert: await getHash(CERT_FILE_PATH)
  457. };
  458. };
  459. async regenerate(record, hosts) {
  460. await this.createCertificate(hosts);
  461. const hash = await this.getLatestHash();
  462. record.update({ hosts, hash });
  463. }
  464. async init() {
  465. await this.config.init();
  466. const exist = await this.checkMkcert();
  467. if (!exist) {
  468. await this.initMkcert();
  469. } else if (this.autoUpgrade) {
  470. await this.upgradeMkcert();
  471. }
  472. }
  473. async getSourceInfo() {
  474. const sourceInfo = await this.source.getSourceInfo();
  475. if (!sourceInfo) {
  476. if (typeof this.sourceType === "string") {
  477. this.logger.error(
  478. "Failed to request mkcert information, please check your network"
  479. );
  480. if (this.sourceType === "github") {
  481. this.logger.info(
  482. 'If you are a user in china, maybe you should set "source" paramter to "coding"'
  483. );
  484. }
  485. } else {
  486. this.logger.info(
  487. 'Please check your custom "source", it seems to return invalid result'
  488. );
  489. }
  490. return void 0;
  491. }
  492. return sourceInfo;
  493. }
  494. async initMkcert() {
  495. const sourceInfo = await this.getSourceInfo();
  496. debug("The mkcert does not exist, download it now");
  497. if (!sourceInfo) {
  498. this.logger.error(
  499. "Can not obtain download information of mkcert, init skipped"
  500. );
  501. return;
  502. }
  503. await this.downloadMkcert(sourceInfo.downloadUrl, this.mkcertSavedPath);
  504. }
  505. async upgradeMkcert() {
  506. const versionManger = new version_default({ config: this.config });
  507. const sourceInfo = await this.getSourceInfo();
  508. if (!sourceInfo) {
  509. this.logger.error(
  510. "Can not obtain download information of mkcert, update skipped"
  511. );
  512. return;
  513. }
  514. const versionInfo = versionManger.compare(sourceInfo.version);
  515. if (!versionInfo.shouldUpdate) {
  516. debug("Mkcert is kept latest version, update skipped");
  517. return;
  518. }
  519. if (versionInfo.breakingChange) {
  520. debug(
  521. "The current version of mkcert is %s, and the latest version is %s, there may be some breaking changes, update skipped",
  522. versionInfo.currentVersion,
  523. versionInfo.nextVersion
  524. );
  525. return;
  526. }
  527. debug(
  528. "The current version of mkcert is %s, and the latest version is %s, mkcert will be updated",
  529. versionInfo.currentVersion,
  530. versionInfo.nextVersion
  531. );
  532. await this.downloadMkcert(sourceInfo.downloadUrl, this.mkcertSavedPath);
  533. versionManger.update(versionInfo.nextVersion);
  534. }
  535. async downloadMkcert(sourceUrl, distPath) {
  536. const downloader = downloader_default.create();
  537. await downloader.download(sourceUrl, distPath);
  538. }
  539. async renew(hosts) {
  540. const record = new record_default({ config: this.config });
  541. if (this.force) {
  542. debug(`Certificate is forced to regenerate`);
  543. await this.regenerate(record, hosts);
  544. }
  545. if (!record.contains(hosts)) {
  546. debug(
  547. `The hosts changed from [${record.getHosts()}] to [${hosts}], start regenerate certificate`
  548. );
  549. await this.regenerate(record, hosts);
  550. return;
  551. }
  552. const hash = await this.getLatestHash();
  553. if (record.tamper(hash)) {
  554. debug(
  555. `The hash changed from ${prettyLog(record.getHash())} to ${prettyLog(
  556. hash
  557. )}, start regenerate certificate`
  558. );
  559. await this.regenerate(record, hosts);
  560. return;
  561. }
  562. debug("Neither hosts nor hash has changed, skip regenerate certificate");
  563. }
  564. async install(hosts) {
  565. if (hosts.length) {
  566. await this.renew(hosts);
  567. }
  568. return await this.getCertificate();
  569. }
  570. };
  571. var mkcert_default = Mkcert;
  572. // plugin/index.ts
  573. var plugin = (options = {}) => {
  574. return {
  575. name: PLUGIN_NAME,
  576. apply: "serve",
  577. config: async ({ server = {}, logLevel }) => {
  578. if (server.https === false) {
  579. return;
  580. }
  581. const { hosts = [], ...mkcertOptions } = options;
  582. const logger = createLogger(logLevel, {
  583. prefix: PLUGIN_NAME
  584. });
  585. const mkcert = mkcert_default.create({
  586. logger,
  587. ...mkcertOptions
  588. });
  589. await mkcert.init();
  590. const allHosts = [...getDefaultHosts(), ...hosts];
  591. if (typeof server.host === "string") {
  592. allHosts.push(server.host);
  593. }
  594. const uniqueHosts = Array.from(new Set(allHosts)).filter((item) => !!item);
  595. const certificate = await mkcert.install(uniqueHosts);
  596. return {
  597. server: {
  598. https: {
  599. ...certificate
  600. }
  601. },
  602. preview: {
  603. https: {
  604. ...certificate
  605. }
  606. }
  607. };
  608. }
  609. };
  610. };
  611. var plugin_default = plugin;
  612. export {
  613. BaseSource,
  614. plugin_default as default
  615. };
  616. //# sourceMappingURL=mkcert.mjs.map