index.js 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598
  1. "use strict";
  2. Object.defineProperty(exports, "__esModule", { value: true });
  3. exports.isValidProjectId = exports.prettyProjectName = exports.Project = exports.MultiProjectConfig = exports.ProjectConfig = exports.createProjectFromDirectory = exports.findProjectDirectory = exports.createProjectFromDetails = exports.ProjectDetails = exports.ProjectDetailsError = void 0;
  4. const tslib_1 = require("tslib");
  5. const cli_framework_1 = require("@ionic/cli-framework");
  6. const fn_1 = require("@ionic/cli-framework/utils/fn");
  7. const node_1 = require("@ionic/cli-framework/utils/node");
  8. const utils_fs_1 = require("@ionic/utils-fs");
  9. const utils_terminal_1 = require("@ionic/utils-terminal");
  10. const debug_1 = require("debug");
  11. const lodash = tslib_1.__importStar(require("lodash"));
  12. const path = tslib_1.__importStar(require("path"));
  13. const constants_1 = require("../../constants");
  14. const guards_1 = require("../../guards");
  15. const color_1 = require("../color");
  16. const errors_1 = require("../errors");
  17. const integrations_1 = require("../integrations");
  18. const color_2 = require("../utils/color");
  19. const debug = (0, debug_1.debug)('ionic:lib:project');
  20. class ProjectDetailsError extends errors_1.BaseException {
  21. constructor(msg,
  22. /**
  23. * Unique code for this error.
  24. */
  25. code,
  26. /**
  27. * The underlying error that caused this error.
  28. */
  29. cause) {
  30. super(msg, { cause });
  31. this.code = code;
  32. }
  33. }
  34. exports.ProjectDetailsError = ProjectDetailsError;
  35. class ProjectDetails {
  36. constructor({ rootDirectory, args = { _: [] }, e }) {
  37. this.rootDirectory = rootDirectory;
  38. this.e = e;
  39. this.args = args;
  40. }
  41. async getIdFromArgs() {
  42. const id = this.args && this.args['project'] ? String(this.args['project']) : undefined;
  43. if (id) {
  44. debug(`Project id from args: ${(0, color_1.strong)(id)}`);
  45. return id;
  46. }
  47. }
  48. async getIdFromPathMatch(config) {
  49. const { ctx } = this.e;
  50. for (const [key, value] of lodash.entries(config.projects)) {
  51. const id = key;
  52. if (value && value.root) {
  53. const projectDir = path.resolve(this.rootDirectory, value.root);
  54. if (ctx.execPath.startsWith(projectDir)) {
  55. debug(`Project id from path match: ${(0, color_1.strong)(id)}`);
  56. return id;
  57. }
  58. }
  59. }
  60. }
  61. async getIdFromDefaultProject(config) {
  62. const id = config.defaultProject;
  63. if (id) {
  64. debug(`Project id from defaultProject: ${(0, color_1.strong)(id)}`);
  65. return id;
  66. }
  67. }
  68. async getTypeFromConfig(config) {
  69. const { type } = config;
  70. if (type) {
  71. debug(`Project type from config: ${(0, color_1.strong)(prettyProjectName(type))} ${type ? (0, color_1.strong)(`(${type})`) : ''}`);
  72. return type;
  73. }
  74. }
  75. async getTypeFromDetection() {
  76. for (const projectType of constants_1.PROJECT_TYPES) {
  77. const p = await createProjectFromDetails({ context: 'app', configPath: path.resolve(this.rootDirectory, constants_1.PROJECT_FILE), type: projectType, errors: [] }, this.e);
  78. const type = p.type;
  79. // TODO: This is a hack to avoid accessing `this.config` within the
  80. // `Project.directory` getter, which writes config files.
  81. Object.defineProperty(p, 'directory', { value: this.rootDirectory, writable: false });
  82. if (await p.detected()) {
  83. debug(`Project type from detection: ${(0, color_1.strong)(prettyProjectName(type))} ${type ? (0, color_1.strong)(`(${type})`) : ''}`);
  84. return type;
  85. }
  86. }
  87. }
  88. async determineSingleApp(config) {
  89. const errors = [];
  90. let type = await (0, fn_1.resolveValue)(async () => this.getTypeFromConfig(config), async () => this.getTypeFromDetection());
  91. if (!type) {
  92. errors.push(new ProjectDetailsError('Could not determine project type', 'ERR_MISSING_PROJECT_TYPE'));
  93. }
  94. else if (!constants_1.PROJECT_TYPES.includes(type)) {
  95. errors.push(new ProjectDetailsError(`Invalid project type: ${type}`, 'ERR_INVALID_PROJECT_TYPE'));
  96. type = undefined;
  97. }
  98. return { context: 'app', type, errors };
  99. }
  100. async determineMultiApp(config) {
  101. const errors = [];
  102. const id = await (0, fn_1.resolveValue)(async () => this.getIdFromArgs(), async () => this.getIdFromPathMatch(config), async () => this.getIdFromDefaultProject(config));
  103. let type;
  104. if (id) {
  105. const app = config.projects[id];
  106. if (app) {
  107. const r = await this.determineSingleApp(app);
  108. type = r.type;
  109. errors.push(...r.errors);
  110. }
  111. else {
  112. errors.push(new ProjectDetailsError('Could not find project in config', 'ERR_MULTI_MISSING_CONFIG'));
  113. }
  114. }
  115. else {
  116. errors.push(new ProjectDetailsError('Could not determine project id', 'ERR_MULTI_MISSING_ID'));
  117. }
  118. return { context: 'multiapp', id, type, errors };
  119. }
  120. processResult(result) {
  121. const { log } = this.e;
  122. const errorCodes = result.errors.map(e => e.code);
  123. const e1 = result.errors.find(e => e.code === 'ERR_INVALID_PROJECT_FILE');
  124. const e2 = result.errors.find(e => e.code === 'ERR_INVALID_PROJECT_TYPE');
  125. if (e1) {
  126. log.error(`Error while loading config (project config: ${(0, color_1.strong)((0, utils_terminal_1.prettyPath)(result.configPath))})\n` +
  127. `${e1.cause ? `${e1.message}: ${(0, color_1.failure)(e1.cause.toString())}` : (0, color_1.failure)(e1.message)}. ` +
  128. `Run ${(0, color_1.input)('ionic init')} to re-initialize your Ionic project. Without a valid project config, the CLI will not have project context.`);
  129. log.nl();
  130. }
  131. if (result.context === 'multiapp') {
  132. if (errorCodes.includes('ERR_MULTI_MISSING_ID')) {
  133. log.warn(`Multi-app workspace detected, but cannot determine which project to use.\n` +
  134. `Please set a ${(0, color_1.input)('defaultProject')} in ${(0, color_1.strong)((0, utils_terminal_1.prettyPath)(result.configPath))} or specify the project using the global ${(0, color_1.input)('--project')} option. Read the documentation${(0, color_1.ancillary)('[1]')} for more information.\n\n` +
  135. `${(0, color_1.ancillary)('[1]')}: ${(0, color_1.strong)('https://ion.link/multi-app-docs')}`);
  136. log.nl();
  137. }
  138. if (result.id && errorCodes.includes('ERR_MULTI_MISSING_CONFIG')) {
  139. log.warn(`Multi-app workspace detected, but project was not found in configuration.\n` +
  140. `Project ${(0, color_1.input)(result.id)} could not be found in the workspace. Did you add it to ${(0, color_1.strong)((0, utils_terminal_1.prettyPath)(result.configPath))}?`);
  141. }
  142. }
  143. if (errorCodes.includes('ERR_MISSING_PROJECT_TYPE')) {
  144. const listWrapOptions = { width: utils_terminal_1.TTY_WIDTH - 8 - 3, indentation: 1 };
  145. log.warn(`Could not determine project type (project config: ${(0, color_1.strong)((0, utils_terminal_1.prettyPath)(result.configPath))}).\n` +
  146. `- ${(0, utils_terminal_1.wordWrap)(`For ${(0, color_1.strong)(prettyProjectName('angular'))} projects, make sure ${(0, color_1.input)('@ionic/angular')} is listed as a dependency in ${(0, color_1.strong)('package.json')}.`, listWrapOptions)}\n` +
  147. `Alternatively, set ${(0, color_1.strong)('type')} attribute in ${(0, color_1.strong)((0, utils_terminal_1.prettyPath)(result.configPath))} to one of: ${constants_1.PROJECT_TYPES.map(v => (0, color_1.input)(v)).join(', ')}.\n\n` +
  148. `If the Ionic CLI does not know what type of project this is, ${(0, color_1.input)('ionic build')}, ${(0, color_1.input)('ionic serve')}, and other commands may not work. You can use the ${(0, color_1.input)('custom')} project type if that's okay.`);
  149. log.nl();
  150. }
  151. if (e2) {
  152. log.error(`${e2.message} (project config: ${(0, color_1.strong)((0, utils_terminal_1.prettyPath)(result.configPath))}).\n` +
  153. `Project type must be one of: ${constants_1.PROJECT_TYPES.map(v => (0, color_1.input)(v)).join(', ')}`);
  154. log.nl();
  155. }
  156. }
  157. async readConfig(p) {
  158. try {
  159. let configContents = await (0, utils_fs_1.readFile)(p, { encoding: 'utf8' });
  160. if (!configContents) {
  161. configContents = '{}\n';
  162. await (0, utils_fs_1.writeFile)(p, configContents, { encoding: 'utf8' });
  163. }
  164. return await JSON.parse(configContents);
  165. }
  166. catch (e) {
  167. throw new ProjectDetailsError('Could not read project file', 'ERR_INVALID_PROJECT_FILE', e);
  168. }
  169. }
  170. /**
  171. * Gather project details from specified configuration.
  172. *
  173. * This method will always resolve with a result object, with an array of
  174. * errors. Use `processResult()` to log warnings & errors.
  175. */
  176. async result() {
  177. const errors = [];
  178. const configPath = path.resolve(this.rootDirectory, constants_1.PROJECT_FILE);
  179. let config;
  180. try {
  181. config = await this.readConfig(configPath);
  182. if ((0, guards_1.isProjectConfig)(config)) {
  183. const r = await this.determineSingleApp(config);
  184. errors.push(...r.errors);
  185. return { ...r, configPath, errors };
  186. }
  187. if ((0, guards_1.isMultiProjectConfig)(config)) {
  188. const r = await this.determineMultiApp(config);
  189. errors.push(...r.errors);
  190. return { ...r, configPath, errors };
  191. }
  192. throw new ProjectDetailsError('Unknown project file structure', 'ERR_INVALID_PROJECT_FILE');
  193. }
  194. catch (e) {
  195. errors.push(e);
  196. }
  197. return { configPath, context: 'unknown', errors };
  198. }
  199. }
  200. exports.ProjectDetails = ProjectDetails;
  201. async function createProjectFromDetails(details, deps) {
  202. const { context, type } = details;
  203. switch (type) {
  204. case 'angular':
  205. case constants_1.ANGULAR_STANDALONE:
  206. const { AngularProject } = await Promise.resolve().then(() => tslib_1.__importStar(require('./angular')));
  207. return new AngularProject(details, deps);
  208. case 'react':
  209. const { ReactProject } = await Promise.resolve().then(() => tslib_1.__importStar(require('./react')));
  210. return new ReactProject(details, deps);
  211. case 'vue':
  212. const { VueProject } = await Promise.resolve().then(() => tslib_1.__importStar(require('./vue')));
  213. return new VueProject(details, deps);
  214. case 'vue-vite':
  215. const { VueViteProject } = await Promise.resolve().then(() => tslib_1.__importStar(require('./vue-vite')));
  216. return new VueViteProject(details, deps);
  217. case 'react-vite':
  218. const { ReactViteProject } = await Promise.resolve().then(() => tslib_1.__importStar(require('./react-vite')));
  219. return new ReactViteProject(details, deps);
  220. case 'custom':
  221. const { CustomProject } = await Promise.resolve().then(() => tslib_1.__importStar(require('./custom')));
  222. return new CustomProject(details, deps);
  223. }
  224. // If we can't match any of the types above, but we've detected a multi-app
  225. // setup, it likely means this is a "bare" project, or a project without
  226. // apps. This can occur when `ionic start` is used for the first time in a
  227. // new multi-app setup.
  228. if (context === 'multiapp') {
  229. const { BareProject } = await Promise.resolve().then(() => tslib_1.__importStar(require('./bare')));
  230. return new BareProject(details, deps);
  231. }
  232. throw new errors_1.FatalException(`Bad project type: ${(0, color_1.strong)(String(type))}`); // TODO?
  233. }
  234. exports.createProjectFromDetails = createProjectFromDetails;
  235. async function findProjectDirectory(cwd) {
  236. return (0, utils_fs_1.findBaseDirectory)(cwd, constants_1.PROJECT_FILE);
  237. }
  238. exports.findProjectDirectory = findProjectDirectory;
  239. async function createProjectFromDirectory(rootDirectory, args, deps, { logErrors = true } = {}) {
  240. const details = new ProjectDetails({ rootDirectory, args, e: deps });
  241. const result = await details.result();
  242. debug('Project details: %o', { ...result, errors: result.errors.map(e => e.code) });
  243. if (logErrors) {
  244. details.processResult(result);
  245. }
  246. if (result.context === 'unknown') {
  247. return;
  248. }
  249. return createProjectFromDetails(result, deps);
  250. }
  251. exports.createProjectFromDirectory = createProjectFromDirectory;
  252. class ProjectConfig extends cli_framework_1.BaseConfig {
  253. constructor(p, { type, ...options } = {}) {
  254. super(p, options);
  255. this.type = type;
  256. const c = this.c;
  257. if (typeof c.app_id === 'string') { // <4.0.0 project config migration
  258. if (c.app_id && !c.id) {
  259. // set `id` only if it has not been previously set and if `app_id`
  260. // isn't an empty string (which it used to be, sometimes)
  261. this.set('id', c.app_id);
  262. }
  263. this.unset('app_id');
  264. }
  265. else if (typeof c.pro_id === 'string') {
  266. if (!c.id) {
  267. // set `id` only if it has not been previously set
  268. this.set('id', c.pro_id);
  269. }
  270. // we do not unset `pro_id` because it would break things
  271. }
  272. }
  273. provideDefaults(c) {
  274. return lodash.assign({
  275. name: 'New Ionic App',
  276. integrations: {},
  277. type: this.type,
  278. }, c);
  279. }
  280. }
  281. exports.ProjectConfig = ProjectConfig;
  282. class MultiProjectConfig extends cli_framework_1.BaseConfig {
  283. provideDefaults(c) {
  284. return lodash.assign({
  285. projects: {},
  286. }, c);
  287. }
  288. }
  289. exports.MultiProjectConfig = MultiProjectConfig;
  290. class Project {
  291. constructor(details, e) {
  292. this.details = details;
  293. this.e = e;
  294. this.rootDirectory = path.dirname(details.configPath);
  295. }
  296. get filePath() {
  297. return this.details.configPath;
  298. }
  299. get directory() {
  300. const root = this.config.get('root');
  301. if (!root) {
  302. return this.rootDirectory;
  303. }
  304. return path.resolve(this.rootDirectory, root);
  305. }
  306. get pathPrefix() {
  307. const id = this.details.context === 'multiapp' ? this.details.id : undefined;
  308. return id ? ['projects', id] : [];
  309. }
  310. get config() {
  311. const options = { type: this.type, pathPrefix: this.pathPrefix };
  312. return new ProjectConfig(this.filePath, options);
  313. }
  314. async getBuildRunner() {
  315. try {
  316. return await this.requireBuildRunner();
  317. }
  318. catch (e) {
  319. if (!(e instanceof errors_1.RunnerNotFoundException)) {
  320. throw e;
  321. }
  322. }
  323. }
  324. async getServeRunner() {
  325. try {
  326. return await this.requireServeRunner();
  327. }
  328. catch (e) {
  329. if (!(e instanceof errors_1.RunnerNotFoundException)) {
  330. throw e;
  331. }
  332. }
  333. }
  334. async getGenerateRunner() {
  335. try {
  336. return await this.requireGenerateRunner();
  337. }
  338. catch (e) {
  339. if (!(e instanceof errors_1.RunnerNotFoundException)) {
  340. throw e;
  341. }
  342. }
  343. }
  344. async requireAppflowId() {
  345. const appflowId = this.config.get('id');
  346. if (!appflowId) {
  347. throw new errors_1.FatalException(`Your project file (${(0, color_1.strong)((0, utils_terminal_1.prettyPath)(this.filePath))}) does not contain '${(0, color_1.strong)('id')}'. ` +
  348. `Run ${(0, color_1.input)('ionic link')}.`);
  349. }
  350. return appflowId;
  351. }
  352. get packageJsonPath() {
  353. return path.resolve(this.directory, 'package.json');
  354. }
  355. async getPackageJson(pkgName, { logErrors = true } = {}) {
  356. let pkg;
  357. let pkgPath;
  358. try {
  359. pkgPath = pkgName ? require.resolve(`${pkgName}/package.json`, { paths: (0, node_1.compileNodeModulesPaths)(this.directory) }) : this.packageJsonPath;
  360. pkg = await (0, node_1.readPackageJsonFile)(pkgPath);
  361. }
  362. catch (e) {
  363. if (logErrors) {
  364. this.e.log.warn(`Error loading ${(0, color_1.strong)(pkgName ? pkgName : `project's`)} ${(0, color_1.strong)('package.json')}: ${e}`);
  365. }
  366. }
  367. return [pkg, pkgPath ? path.dirname(pkgPath) : undefined];
  368. }
  369. async requirePackageJson(pkgName) {
  370. try {
  371. const pkgPath = pkgName ? require.resolve(`${pkgName}/package.json`, { paths: (0, node_1.compileNodeModulesPaths)(this.directory) }) : this.packageJsonPath;
  372. return await (0, node_1.readPackageJsonFile)(pkgPath);
  373. }
  374. catch (e) {
  375. if (e instanceof SyntaxError) {
  376. throw new errors_1.FatalException(`Could not parse ${(0, color_1.strong)(pkgName ? pkgName : `project's`)} ${(0, color_1.strong)('package.json')}. Is it a valid JSON file?`);
  377. }
  378. else if (e === node_1.ERROR_INVALID_PACKAGE_JSON) {
  379. throw new errors_1.FatalException(`The ${(0, color_1.strong)(pkgName ? pkgName : `project's`)} ${(0, color_1.strong)('package.json')} file seems malformed.`);
  380. }
  381. throw e; // Probably file not found
  382. }
  383. }
  384. async getSourceDir() {
  385. return path.resolve(this.directory, 'src');
  386. }
  387. async getDefaultDistDir() {
  388. return 'www';
  389. }
  390. async getDistDir() {
  391. if (this.getIntegration('capacitor') !== undefined) {
  392. const capacitor = await this.createIntegration('capacitor');
  393. const conf = await capacitor.getCapacitorConfig();
  394. const webDir = conf?.webDir;
  395. if (webDir) {
  396. return path.resolve(capacitor.root, webDir);
  397. }
  398. else {
  399. throw new errors_1.FatalException(`The ${(0, color_1.input)('webDir')} property must be set in the Capacitor configuration file. \n` +
  400. `See the Capacitor docs for more information: ${(0, color_1.strong)('https://capacitor.ionicframework.com/docs/basics/configuring-your-app')}`);
  401. }
  402. }
  403. else {
  404. return path.resolve(this.directory, 'www');
  405. }
  406. }
  407. async getInfo() {
  408. const integrations = await this.getIntegrations();
  409. const integrationInfo = lodash.flatten(await Promise.all(integrations.map(async (i) => i.getInfo())));
  410. return integrationInfo;
  411. }
  412. async personalize(details) {
  413. const { name, projectId, description, version, themeColor, appIcon, splash } = details;
  414. this.config.set('name', name);
  415. const pkg = await this.requirePackageJson();
  416. pkg.name = projectId;
  417. pkg.version = version ? version : '0.0.1';
  418. pkg.description = description ? description : 'An Ionic project';
  419. await (0, utils_fs_1.writeJson)(this.packageJsonPath, pkg, { spaces: 2 });
  420. if (themeColor) {
  421. await this.setPrimaryTheme(themeColor);
  422. }
  423. if (appIcon && splash) {
  424. await this.setAppResources(appIcon, splash);
  425. }
  426. const integrations = await this.getIntegrations();
  427. await Promise.all(integrations.map(async (i) => i.personalize(details)));
  428. }
  429. // Empty to avoid sub-classes having to implement
  430. async setPrimaryTheme(_themeColor) { }
  431. async writeThemeColor(variablesPath, themeColor) {
  432. const light = new color_2.Color(themeColor);
  433. const ionicThemeLightDarkMap = {
  434. '#3880ff': '#4c8dff',
  435. '#5260ff': '#6a64ff',
  436. '#2dd36f': '#2fdf75',
  437. '#ffc409': '#ffd534',
  438. '#eb445a': '#ff4961',
  439. '#f4f5f8': '#222428',
  440. '#92949c': '#989aa2',
  441. '#222428': '#f4f5f8', // dark
  442. };
  443. const matchingThemeColor = ionicThemeLightDarkMap[themeColor];
  444. let dark;
  445. // If this is a standard Ionic theme color, then use the hard-coded dark mode
  446. // colors. Otherwise, compute a plausible dark mode color for this theme
  447. if (matchingThemeColor) {
  448. dark = new color_2.Color(matchingThemeColor);
  449. }
  450. else if (light.yiq > 128) {
  451. // Light mode was light enough, just use it for both
  452. dark = light;
  453. }
  454. else {
  455. // Light mode was too dark, so tint it to make it brighter
  456. dark = light.tint(0.6);
  457. }
  458. // Build the light colors
  459. const lightContrastRgb = light.contrast().rgb;
  460. const lightVariables = {
  461. '--ion-color-primary': `${themeColor}`,
  462. '--ion-color-primary-rgb': `${light.rgb.r}, ${light.rgb.g}, ${light.rgb.b}`,
  463. '--ion-color-primary-contrast': `${light.contrast().hex}`,
  464. '--ion-color-primary-contrast-rgb': `${lightContrastRgb.r}, ${lightContrastRgb.g}, ${lightContrastRgb.b}`,
  465. '--ion-color-primary-shade': `${light.shade().hex}`,
  466. '--ion-color-primary-tint': `${light.tint().hex}`,
  467. };
  468. const darkContrastRgb = dark.contrast().rgb;
  469. const darkVariables = {
  470. '--ion-color-primary': `${dark.hex}`,
  471. '--ion-color-primary-rgb': `${dark.rgb.r}, ${dark.rgb.g}, ${dark.rgb.b}`,
  472. '--ion-color-primary-contrast': `${dark.contrast().hex}`,
  473. '--ion-color-primary-contrast-rgb': `${darkContrastRgb.r}, ${darkContrastRgb.g}, ${darkContrastRgb.b}`,
  474. '--ion-color-primary-shade': `${dark.shade().hex}`,
  475. '--ion-color-primary-tint': `${dark.tint().hex}`,
  476. };
  477. try {
  478. let themeVarsContents = await (0, utils_fs_1.readFile)(variablesPath, { encoding: 'utf8' });
  479. // Replace every theme variable with the updated ones
  480. for (const v in lightVariables) {
  481. const regExp = new RegExp(`(${v}):([^;]*)`, 'g');
  482. let variableIndex = 0;
  483. themeVarsContents = themeVarsContents.replace(regExp, (str, match) => {
  484. if (variableIndex === 0) {
  485. variableIndex++;
  486. return `${match}: ${lightVariables[v]}`;
  487. }
  488. return str;
  489. });
  490. }
  491. for (const v in darkVariables) {
  492. const regExp = new RegExp(`(${v}):([^;]*)`, 'g');
  493. let variableIndex = 0;
  494. themeVarsContents = themeVarsContents.replace(regExp, (str, match) => {
  495. if (variableIndex === 1) {
  496. return `${match}: ${darkVariables[v]}`;
  497. }
  498. variableIndex++;
  499. return str;
  500. });
  501. }
  502. await (0, utils_fs_1.writeFile)(variablesPath, themeVarsContents);
  503. }
  504. catch (e) {
  505. const { log } = this.e;
  506. log.error(`Unable to modify theme variables, theme will need to be set manually: ${e}`);
  507. }
  508. }
  509. async setAppResources(appIcon, splash) {
  510. const resourcesDir = path.join(this.directory, 'resources');
  511. const iconPath = path.join(resourcesDir, 'icon.png');
  512. const splashPath = path.join(resourcesDir, 'splash.png');
  513. try {
  514. await (0, utils_fs_1.ensureDir)(resourcesDir);
  515. await (0, utils_fs_1.writeFile)(iconPath, appIcon);
  516. await (0, utils_fs_1.writeFile)(splashPath, splash);
  517. }
  518. catch (e) {
  519. const { log } = this.e;
  520. log.error(`Unable to find or create the resources directory. Skipping icon generation: ${e}`);
  521. }
  522. }
  523. async createIntegration(name) {
  524. return integrations_1.BaseIntegration.createFromName({
  525. client: this.e.client,
  526. config: this.e.config,
  527. log: this.e.log,
  528. project: this,
  529. prompt: this.e.prompt,
  530. session: this.e.session,
  531. shell: this.e.shell,
  532. }, name);
  533. }
  534. getIntegration(name) {
  535. const integration = this.config.get('integrations')[name];
  536. if (integration) {
  537. return {
  538. enabled: integration.enabled !== false,
  539. root: integration.root === undefined ? this.directory : path.resolve(this.rootDirectory, integration.root),
  540. };
  541. }
  542. }
  543. requireIntegration(name) {
  544. const id = this.details.context === 'multiapp' ? this.details.id : undefined;
  545. const integration = this.getIntegration(name);
  546. if (!integration) {
  547. throw new errors_1.FatalException(`Could not find ${(0, color_1.strong)(name)} integration in the ${(0, color_1.strong)(id ? id : 'default')} project.`);
  548. }
  549. if (!integration.enabled) {
  550. throw new errors_1.FatalException(`${(0, color_1.strong)(name)} integration is disabled in the ${(0, color_1.strong)(id ? id : 'default')} project.`);
  551. }
  552. return integration;
  553. }
  554. async getIntegrations() {
  555. const integrationsFromConfig = this.config.get('integrations');
  556. const names = Object.keys(integrationsFromConfig); // TODO
  557. const integrationNames = names.filter(n => {
  558. const c = integrationsFromConfig[n];
  559. return c && c.enabled !== false;
  560. });
  561. const integrations = await Promise.all(integrationNames.map(async (name) => {
  562. try {
  563. return await this.createIntegration(name);
  564. }
  565. catch (e) {
  566. if (!(e instanceof errors_1.IntegrationNotFoundException)) {
  567. throw e;
  568. }
  569. this.e.log.warn(e.message);
  570. }
  571. }));
  572. return integrations.filter((i) => typeof i !== 'undefined');
  573. }
  574. }
  575. exports.Project = Project;
  576. function prettyProjectName(type) {
  577. if (!type) {
  578. return 'Unknown';
  579. }
  580. if (type === 'angular') {
  581. return '@ionic/angular';
  582. }
  583. else if (type === 'react') {
  584. return '@ionic/react';
  585. }
  586. else if (type === 'vue') {
  587. return '@ionic/vue';
  588. }
  589. else if (type === 'custom') {
  590. return 'Custom';
  591. }
  592. return type;
  593. }
  594. exports.prettyProjectName = prettyProjectName;
  595. function isValidProjectId(projectId) {
  596. return projectId !== '.' && (0, node_1.isValidPackageName)(projectId) && projectId === path.basename(projectId);
  597. }
  598. exports.isValidProjectId = isValidProjectId;