12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333233423352336233723382339234023412342234323442345234623472348234923502351235223532354235523562357235823592360236123622363236423652366236723682369237023712372237323742375237623772378237923802381238223832384238523862387238823892390239123922393239423952396239723982399240024012402240324042405240624072408240924102411241224132414241524162417241824192420242124222423242424252426242724282429243024312432243324342435 |
- /*!
- Stencil CLI (CommonJS) v2.18.1 | MIT Licensed | https://stenciljs.com
- */
- 'use strict';
- Object.defineProperty(exports, '__esModule', { value: true });
- function _interopNamespace(e) {
- if (e && e.__esModule) return e;
- var n = Object.create(null);
- if (e) {
- Object.keys(e).forEach(function (k) {
- if (k !== 'default') {
- var d = Object.getOwnPropertyDescriptor(e, k);
- Object.defineProperty(n, k, d.get ? d : {
- enumerable: true,
- get: function () {
- return e[k];
- }
- });
- }
- });
- }
- n['default'] = e;
- return Object.freeze(n);
- }
- /**
- * Convert a string from PascalCase to dash-case
- *
- * @param str the string to convert
- * @returns a converted string
- */
- const toDashCase = (str) => str
- .replace(/([A-Z0-9])/g, (match) => ` ${match[0]}`)
- .trim()
- .split(' ')
- .join('-')
- .toLowerCase();
- /**
- * Convert a string from dash-case / kebab-case to PascalCase (or CamelCase,
- * or whatever you call it!)
- *
- * @param str a string to convert
- * @returns a converted string
- */
- const dashToPascalCase = (str) => str
- .toLowerCase()
- .split('-')
- .map((segment) => segment.charAt(0).toUpperCase() + segment.slice(1))
- .join('');
- const isFunction = (v) => typeof v === 'function';
- const isString = (v) => typeof v === 'string';
- /**
- * Builds a template `Diagnostic` entity for a build error. The created `Diagnostic` is returned, and have little
- * detail attached to it regarding the specifics of the error - it is the responsibility of the caller of this method
- * to attach the specifics of the error message.
- *
- * The created `Diagnostic` is pushed to the `diagnostics` argument as a side effect of calling this method.
- *
- * @param diagnostics the existing diagnostics that the created template `Diagnostic` should be added to
- * @returns the created `Diagnostic`
- */
- const buildError = (diagnostics) => {
- const diagnostic = {
- level: 'error',
- type: 'build',
- header: 'Build Error',
- messageText: 'build error',
- relFilePath: null,
- absFilePath: null,
- lines: [],
- };
- if (diagnostics) {
- diagnostics.push(diagnostic);
- }
- return diagnostic;
- };
- /**
- * Builds a diagnostic from an `Error`, appends it to the `diagnostics` parameter, and returns the created diagnostic
- * @param diagnostics the series of diagnostics the newly created diagnostics should be added to
- * @param err the error to derive information from in generating the diagnostic
- * @param msg an optional message to use in place of `err` to generate the diagnostic
- * @returns the generated diagnostic
- */
- const catchError = (diagnostics, err, msg) => {
- const diagnostic = {
- level: 'error',
- type: 'build',
- header: 'Build Error',
- messageText: 'build error',
- relFilePath: null,
- absFilePath: null,
- lines: [],
- };
- if (isString(msg)) {
- diagnostic.messageText = msg.length ? msg : 'UNKNOWN ERROR';
- }
- else if (err != null) {
- if (err.stack != null) {
- diagnostic.messageText = err.stack.toString();
- }
- else {
- if (err.message != null) {
- diagnostic.messageText = err.message.length ? err.message : 'UNKNOWN ERROR';
- }
- else {
- diagnostic.messageText = err.toString();
- }
- }
- }
- if (diagnostics != null && !shouldIgnoreError(diagnostic.messageText)) {
- diagnostics.push(diagnostic);
- }
- return diagnostic;
- };
- /**
- * Determine if the provided diagnostics have any build errors
- * @param diagnostics the diagnostics to inspect
- * @returns true if any of the diagnostics in the list provided are errors that did not occur at runtime. false
- * otherwise.
- */
- const hasError = (diagnostics) => {
- if (diagnostics == null || diagnostics.length === 0) {
- return false;
- }
- return diagnostics.some((d) => d.level === 'error' && d.type !== 'runtime');
- };
- const shouldIgnoreError = (msg) => {
- return msg === TASK_CANCELED_MSG;
- };
- const TASK_CANCELED_MSG = `task canceled`;
- /**
- * Convert Windows backslash paths to slash paths: foo\\bar ➔ foo/bar
- * Forward-slash paths can be used in Windows as long as they're not
- * extended-length paths and don't contain any non-ascii characters.
- * This was created since the path methods in Node.js outputs \\ paths on Windows.
- * @param path the Windows-based path to convert
- * @returns the converted path
- */
- const normalizePath = (path) => {
- if (typeof path !== 'string') {
- throw new Error(`invalid path to normalize`);
- }
- path = normalizeSlashes(path.trim());
- const components = pathComponents(path, getRootLength(path));
- const reducedComponents = reducePathComponents(components);
- const rootPart = reducedComponents[0];
- const secondPart = reducedComponents[1];
- const normalized = rootPart + reducedComponents.slice(1).join('/');
- if (normalized === '') {
- return '.';
- }
- if (rootPart === '' &&
- secondPart &&
- path.includes('/') &&
- !secondPart.startsWith('.') &&
- !secondPart.startsWith('@')) {
- return './' + normalized;
- }
- return normalized;
- };
- const normalizeSlashes = (path) => path.replace(backslashRegExp, '/');
- const altDirectorySeparator = '\\';
- const urlSchemeSeparator = '://';
- const backslashRegExp = /\\/g;
- const reducePathComponents = (components) => {
- if (!Array.isArray(components) || components.length === 0) {
- return [];
- }
- const reduced = [components[0]];
- for (let i = 1; i < components.length; i++) {
- const component = components[i];
- if (!component)
- continue;
- if (component === '.')
- continue;
- if (component === '..') {
- if (reduced.length > 1) {
- if (reduced[reduced.length - 1] !== '..') {
- reduced.pop();
- continue;
- }
- }
- else if (reduced[0])
- continue;
- }
- reduced.push(component);
- }
- return reduced;
- };
- const getRootLength = (path) => {
- const rootLength = getEncodedRootLength(path);
- return rootLength < 0 ? ~rootLength : rootLength;
- };
- const getEncodedRootLength = (path) => {
- if (!path)
- return 0;
- const ch0 = path.charCodeAt(0);
- // POSIX or UNC
- if (ch0 === 47 /* CharacterCodes.slash */ || ch0 === 92 /* CharacterCodes.backslash */) {
- if (path.charCodeAt(1) !== ch0)
- return 1; // POSIX: "/" (or non-normalized "\")
- const p1 = path.indexOf(ch0 === 47 /* CharacterCodes.slash */ ? '/' : altDirectorySeparator, 2);
- if (p1 < 0)
- return path.length; // UNC: "//server" or "\\server"
- return p1 + 1; // UNC: "//server/" or "\\server\"
- }
- // DOS
- if (isVolumeCharacter(ch0) && path.charCodeAt(1) === 58 /* CharacterCodes.colon */) {
- const ch2 = path.charCodeAt(2);
- if (ch2 === 47 /* CharacterCodes.slash */ || ch2 === 92 /* CharacterCodes.backslash */)
- return 3; // DOS: "c:/" or "c:\"
- if (path.length === 2)
- return 2; // DOS: "c:" (but not "c:d")
- }
- // URL
- const schemeEnd = path.indexOf(urlSchemeSeparator);
- if (schemeEnd !== -1) {
- const authorityStart = schemeEnd + urlSchemeSeparator.length;
- const authorityEnd = path.indexOf('/', authorityStart);
- if (authorityEnd !== -1) {
- // URL: "file:///", "file://server/", "file://server/path"
- // For local "file" URLs, include the leading DOS volume (if present).
- // Per https://www.ietf.org/rfc/rfc1738.txt, a host of "" or "localhost" is a
- // special case interpreted as "the machine from which the URL is being interpreted".
- const scheme = path.slice(0, schemeEnd);
- const authority = path.slice(authorityStart, authorityEnd);
- if (scheme === 'file' &&
- (authority === '' || authority === 'localhost') &&
- isVolumeCharacter(path.charCodeAt(authorityEnd + 1))) {
- const volumeSeparatorEnd = getFileUrlVolumeSeparatorEnd(path, authorityEnd + 2);
- if (volumeSeparatorEnd !== -1) {
- if (path.charCodeAt(volumeSeparatorEnd) === 47 /* CharacterCodes.slash */) {
- // URL: "file:///c:/", "file://localhost/c:/", "file:///c%3a/", "file://localhost/c%3a/"
- return ~(volumeSeparatorEnd + 1);
- }
- if (volumeSeparatorEnd === path.length) {
- // URL: "file:///c:", "file://localhost/c:", "file:///c$3a", "file://localhost/c%3a"
- // but not "file:///c:d" or "file:///c%3ad"
- return ~volumeSeparatorEnd;
- }
- }
- }
- return ~(authorityEnd + 1); // URL: "file://server/", "http://server/"
- }
- return ~path.length; // URL: "file://server", "http://server"
- }
- // relative
- return 0;
- };
- const isVolumeCharacter = (charCode) => (charCode >= 97 /* CharacterCodes.a */ && charCode <= 122 /* CharacterCodes.z */) ||
- (charCode >= 65 /* CharacterCodes.A */ && charCode <= 90 /* CharacterCodes.Z */);
- const getFileUrlVolumeSeparatorEnd = (url, start) => {
- const ch0 = url.charCodeAt(start);
- if (ch0 === 58 /* CharacterCodes.colon */)
- return start + 1;
- if (ch0 === 37 /* CharacterCodes.percent */ && url.charCodeAt(start + 1) === 51 /* CharacterCodes._3 */) {
- const ch2 = url.charCodeAt(start + 2);
- if (ch2 === 97 /* CharacterCodes.a */ || ch2 === 65 /* CharacterCodes.A */)
- return start + 3;
- }
- return -1;
- };
- const pathComponents = (path, rootLength) => {
- const root = path.substring(0, rootLength);
- const rest = path.substring(rootLength).split('/');
- const restLen = rest.length;
- if (restLen > 0 && !rest[restLen - 1]) {
- rest.pop();
- }
- return [root, ...rest];
- };
- /**
- * Check whether a string is a member of a ReadonlyArray<string>
- *
- * We need a little helper for this because unfortunately `includes` is typed
- * on `ReadonlyArray<T>` as `(el: T): boolean` so a `string` cannot be passed
- * to `includes` on a `ReadonlyArray` 😢 thus we have a little helper function
- * where we do the type coercion just once.
- *
- * see microsoft/TypeScript#31018 for some discussion of this
- *
- * @param readOnlyArray the array we're checking
- * @param maybeMember a value which is possibly a member of the array
- * @returns whether the array contains the member or not
- */
- const readOnlyArrayHasStringMember = (readOnlyArray, maybeMember) => readOnlyArray.includes(maybeMember);
- /**
- * Validates that a component tag meets required naming conventions to be used for a web component
- * @param tag the tag to validate
- * @returns an error message if the tag has an invalid name, undefined if the tag name passes all checks
- */
- const validateComponentTag = (tag) => {
- // we want to check this first since we call some String.prototype methods below
- if (typeof tag !== 'string') {
- return `Tag "${tag}" must be a string type`;
- }
- if (tag !== tag.trim()) {
- return `Tag can not contain white spaces`;
- }
- if (tag !== tag.toLowerCase()) {
- return `Tag can not contain upper case characters`;
- }
- if (tag.length === 0) {
- return `Received empty tag value`;
- }
- if (tag.indexOf(' ') > -1) {
- return `"${tag}" tag cannot contain a space`;
- }
- if (tag.indexOf(',') > -1) {
- return `"${tag}" tag cannot be used for multiple tags`;
- }
- const invalidChars = tag.replace(/\w|-/g, '');
- if (invalidChars !== '') {
- return `"${tag}" tag contains invalid characters: ${invalidChars}`;
- }
- if (tag.indexOf('-') === -1) {
- return `"${tag}" tag must contain a dash (-) to work as a valid web component`;
- }
- if (tag.indexOf('--') > -1) {
- return `"${tag}" tag cannot contain multiple dashes (--) next to each other`;
- }
- if (tag.indexOf('-') === 0) {
- return `"${tag}" tag cannot start with a dash (-)`;
- }
- if (tag.lastIndexOf('-') === tag.length - 1) {
- return `"${tag}" tag cannot end with a dash (-)`;
- }
- return undefined;
- };
- /**
- * This sets the log level hierarchy for our terminal logger, ranging from
- * most to least verbose.
- *
- * Ordering the levels like this lets us easily check whether we should log a
- * message at a given time. For instance, if the log level is set to `'warn'`,
- * then anything passed to the logger with level `'warn'` or `'error'` should
- * be logged, but we should _not_ log anything with level `'info'` or `'debug'`.
- *
- * If we have a current log level `currentLevel` and a message with level
- * `msgLevel` is passed to the logger, we can determine whether or not we should
- * log it by checking if the log level on the message is further up or at the
- * same level in the hierarchy than `currentLevel`, like so:
- *
- * ```ts
- * LOG_LEVELS.indexOf(msgLevel) >= LOG_LEVELS.indexOf(currentLevel)
- * ```
- *
- * NOTE: for the reasons described above, do not change the order of the entries
- * in this array without good reason!
- */
- const LOG_LEVELS = ['debug', 'info', 'warn', 'error'];
- /**
- * All the Boolean options supported by the Stencil CLI
- */
- const BOOLEAN_CLI_ARGS = [
- 'build',
- 'cache',
- 'checkVersion',
- 'ci',
- 'compare',
- 'debug',
- 'dev',
- 'devtools',
- 'docs',
- 'e2e',
- 'es5',
- 'esm',
- 'headless',
- 'help',
- 'log',
- 'open',
- 'prerender',
- 'prerenderExternal',
- 'prod',
- 'profile',
- 'serviceWorker',
- 'screenshot',
- 'serve',
- 'skipNodeCheck',
- 'spec',
- 'ssr',
- 'stats',
- 'updateScreenshot',
- 'verbose',
- 'version',
- 'watch',
- // JEST CLI OPTIONS
- 'all',
- 'automock',
- 'bail',
- // 'cache', Stencil already supports this argument
- 'changedFilesWithAncestor',
- // 'ci', Stencil already supports this argument
- 'clearCache',
- 'clearMocks',
- 'collectCoverage',
- 'color',
- 'colors',
- 'coverage',
- // 'debug', Stencil already supports this argument
- 'detectLeaks',
- 'detectOpenHandles',
- 'errorOnDeprecated',
- 'expand',
- 'findRelatedTests',
- 'forceExit',
- 'init',
- 'injectGlobals',
- 'json',
- 'lastCommit',
- 'listTests',
- 'logHeapUsage',
- 'noStackTrace',
- 'notify',
- 'onlyChanged',
- 'onlyFailures',
- 'passWithNoTests',
- 'resetMocks',
- 'resetModules',
- 'restoreMocks',
- 'runInBand',
- 'runTestsByPath',
- 'showConfig',
- 'silent',
- 'skipFilter',
- 'testLocationInResults',
- 'updateSnapshot',
- 'useStderr',
- // 'verbose', Stencil already supports this argument
- // 'version', Stencil already supports this argument
- // 'watch', Stencil already supports this argument
- 'watchAll',
- 'watchman',
- ];
- /**
- * All the Number options supported by the Stencil CLI
- */
- const NUMBER_CLI_ARGS = [
- 'port',
- // JEST CLI ARGS
- 'maxConcurrency',
- 'testTimeout',
- ];
- /**
- * All the String options supported by the Stencil CLI
- */
- const STRING_CLI_ARGS = [
- 'address',
- 'config',
- 'docsApi',
- 'docsJson',
- 'emulate',
- 'root',
- 'screenshotConnector',
- // JEST CLI ARGS
- 'cacheDirectory',
- 'changedSince',
- 'collectCoverageFrom',
- // 'config', Stencil already supports this argument
- 'coverageDirectory',
- 'coverageThreshold',
- 'env',
- 'filter',
- 'globalSetup',
- 'globalTeardown',
- 'globals',
- 'haste',
- 'moduleNameMapper',
- 'notifyMode',
- 'outputFile',
- 'preset',
- 'prettierPath',
- 'resolver',
- 'rootDir',
- 'runner',
- 'testEnvironment',
- 'testEnvironmentOptions',
- 'testFailureExitCode',
- 'testNamePattern',
- 'testResultsProcessor',
- 'testRunner',
- 'testSequencer',
- 'testURL',
- 'timers',
- 'transform',
- // ARRAY ARGS
- 'collectCoverageOnlyFrom',
- 'coveragePathIgnorePatterns',
- 'coverageReporters',
- 'moduleDirectories',
- 'moduleFileExtensions',
- 'modulePathIgnorePatterns',
- 'modulePaths',
- 'projects',
- 'reporters',
- 'roots',
- 'selectProjects',
- 'setupFiles',
- 'setupFilesAfterEnv',
- 'snapshotSerializers',
- 'testMatch',
- 'testPathIgnorePatterns',
- 'testPathPattern',
- 'testRegex',
- 'transformIgnorePatterns',
- 'unmockedModulePathPatterns',
- 'watchPathIgnorePatterns',
- ];
- /**
- * All the CLI arguments which may have string or number values
- *
- * `maxWorkers` is an argument which is used both by Stencil _and_ by Jest,
- * which means that we need to support parsing both string and number values.
- */
- const STRING_NUMBER_CLI_ARGS = ['maxWorkers'];
- /**
- * All the LogLevel-type options supported by the Stencil CLI
- *
- * This is a bit silly since there's only one such argument atm,
- * but this approach lets us make sure that we're handling all
- * our arguments in a type-safe way.
- */
- const LOG_LEVEL_CLI_ARGS = ['logLevel'];
- /**
- * For a small subset of CLI options we support a short alias e.g. `'h'` for `'help'`
- */
- const CLI_ARG_ALIASES = {
- config: 'c',
- help: 'h',
- port: 'p',
- version: 'v',
- };
- /**
- * Helper function for initializing a `ConfigFlags` object. Provide any overrides
- * for default values and off you go!
- *
- * @param init an object with any overrides for default values
- * @returns a complete CLI flag object
- */
- const createConfigFlags = (init = {}) => {
- const flags = {
- task: null,
- args: [],
- knownArgs: [],
- unknownArgs: [],
- ...init,
- };
- return flags;
- };
- /**
- * Parse command line arguments into a structured `ConfigFlags` object
- *
- * @param args an array of CLI flags
- * @param _sys an optional compiler system
- * @returns a structured ConfigFlags object
- */
- const parseFlags = (args, _sys) => {
- // TODO(STENCIL-509): remove the _sys parameter here ^^ (for v3)
- const flags = createConfigFlags();
- // cmd line has more priority over npm scripts cmd
- flags.args = Array.isArray(args) ? args.slice() : [];
- if (flags.args.length > 0 && flags.args[0] && !flags.args[0].startsWith('-')) {
- flags.task = flags.args[0];
- }
- parseArgs(flags, flags.args);
- if (flags.task != null) {
- const i = flags.args.indexOf(flags.task);
- if (i > -1) {
- flags.args.splice(i, 1);
- }
- }
- // to find unknown / unrecognized arguments we filter `args`, including only
- // arguments whose normalized form is not found in `knownArgs`. `knownArgs`
- // is populated during the call to `parseArgs` above. For arguments like
- // `--foobar` the string `"--foobar"` will be added, while for more
- // complicated arguments like `--bizBoz=bop` or `--bizBoz bop` just the
- // string `"--bizBoz"` will be added.
- flags.unknownArgs = flags.args.filter((arg) => !flags.knownArgs.includes(parseEqualsArg(arg)[0]));
- return flags;
- };
- /**
- * Parse command line arguments that are enumerated in the `config-flags`
- * module. Handles leading dashes on arguments, aliases that are defined for a
- * small number of arguments, and parsing values for non-boolean arguments
- * (e.g. port number for the dev server).
- *
- * @param flags a ConfigFlags object to which parsed arguments will be added
- * @param args an array of command-line arguments to parse
- */
- const parseArgs = (flags, args) => {
- BOOLEAN_CLI_ARGS.forEach((argName) => parseBooleanArg(flags, args, argName));
- STRING_CLI_ARGS.forEach((argName) => parseStringArg(flags, args, argName));
- NUMBER_CLI_ARGS.forEach((argName) => parseNumberArg(flags, args, argName));
- STRING_NUMBER_CLI_ARGS.forEach((argName) => parseStringNumberArg(flags, args, argName));
- LOG_LEVEL_CLI_ARGS.forEach((argName) => parseLogLevelArg(flags, args, argName));
- };
- /**
- * Parse a boolean CLI argument. For these, we support the following formats:
- *
- * - `--booleanArg`
- * - `--boolean-arg`
- * - `--noBooleanArg`
- * - `--no-boolean-arg`
- *
- * The final two variants should be parsed to a value of `false` on the config
- * object.
- *
- * @param flags the config flags object, while we'll modify
- * @param args our CLI arguments
- * @param configCaseName the argument we want to look at right now
- */
- const parseBooleanArg = (flags, args, configCaseName) => {
- // we support both dash-case and PascalCase versions of the parameter
- // argName is 'configCase' version which can be found in BOOLEAN_ARG_OPTS
- const alias = CLI_ARG_ALIASES[configCaseName];
- const dashCaseName = toDashCase(configCaseName);
- if (typeof flags[configCaseName] !== 'boolean') {
- flags[configCaseName] = null;
- }
- args.forEach((cmdArg) => {
- let value;
- if (cmdArg === `--${configCaseName}` || cmdArg === `--${dashCaseName}`) {
- value = true;
- }
- else if (cmdArg === `--no-${dashCaseName}` || cmdArg === `--no${dashToPascalCase(dashCaseName)}`) {
- value = false;
- }
- else if (alias && cmdArg === `-${alias}`) {
- value = true;
- }
- if (value !== undefined && cmdArg !== undefined) {
- flags[configCaseName] = value;
- flags.knownArgs.push(cmdArg);
- }
- });
- };
- /**
- * Parse a string CLI argument
- *
- * @param flags the config flags object, while we'll modify
- * @param args our CLI arguments
- * @param configCaseName the argument we want to look at right now
- */
- const parseStringArg = (flags, args, configCaseName) => {
- if (typeof flags[configCaseName] !== 'string') {
- flags[configCaseName] = null;
- }
- const { value, matchingArg } = getValue(args, configCaseName);
- if (value !== undefined && matchingArg !== undefined) {
- flags[configCaseName] = value;
- flags.knownArgs.push(matchingArg);
- flags.knownArgs.push(value);
- }
- };
- /**
- * Parse a number CLI argument
- *
- * @param flags the config flags object, while we'll modify
- * @param args our CLI arguments
- * @param configCaseName the argument we want to look at right now
- */
- const parseNumberArg = (flags, args, configCaseName) => {
- if (typeof flags[configCaseName] !== 'number') {
- flags[configCaseName] = null;
- }
- const { value, matchingArg } = getValue(args, configCaseName);
- if (value !== undefined && matchingArg !== undefined) {
- flags[configCaseName] = parseInt(value, 10);
- flags.knownArgs.push(matchingArg);
- flags.knownArgs.push(value);
- }
- };
- /**
- * Parse a CLI argument which may be either a string or a number
- *
- * @param flags the config flags object, while we'll modify
- * @param args our CLI arguments
- * @param configCaseName the argument we want to look at right now
- */
- const parseStringNumberArg = (flags, args, configCaseName) => {
- if (!['number', 'string'].includes(typeof flags[configCaseName])) {
- flags[configCaseName] = null;
- }
- const { value, matchingArg } = getValue(args, configCaseName);
- if (value !== undefined && matchingArg !== undefined) {
- if (CLI_ARG_STRING_REGEX.test(value)) {
- // if it matches the regex we treat it like a string
- flags[configCaseName] = value;
- }
- else {
- // it was a number, great!
- flags[configCaseName] = Number(value);
- }
- flags.knownArgs.push(matchingArg);
- flags.knownArgs.push(value);
- }
- };
- /**
- * We use this regular expression to detect CLI parameters which
- * should be parsed as string values (as opposed to numbers) for
- * the argument types for which we support both a string and a
- * number value.
- *
- * The regex tests for the presence of at least one character which is
- * _not_ a digit (`\d`), a period (`\.`), or one of the characters `"e"`,
- * `"E"`, `"+"`, or `"-"` (the latter four characters are necessary to
- * support the admittedly unlikely use of scientific notation, like `"4e+0"`
- * for `4`).
- *
- * Thus we'll match a string like `"50%"`, but not a string like `"50"` or
- * `"5.0"`. If it matches a given string we conclude that the string should
- * be parsed as a string literal, rather than using `Number` to convert it
- * to a number.
- */
- const CLI_ARG_STRING_REGEX = /[^\d\.Ee\+\-]+/g;
- /**
- * Parse a LogLevel CLI argument. These can be only a specific
- * set of strings, so this function takes care of validating that
- * the value is correct.
- *
- * @param flags the config flags object, while we'll modify
- * @param args our CLI arguments
- * @param configCaseName the argument we want to look at right now
- */
- const parseLogLevelArg = (flags, args, configCaseName) => {
- if (typeof flags[configCaseName] !== 'string') {
- flags[configCaseName] = null;
- }
- const { value, matchingArg } = getValue(args, configCaseName);
- if (value !== undefined && matchingArg !== undefined && isLogLevel(value)) {
- flags[configCaseName] = value;
- flags.knownArgs.push(matchingArg);
- flags.knownArgs.push(value);
- }
- };
- /**
- * Helper for pulling values out from the raw array of CLI arguments. This logic
- * is shared between a few different types of CLI args.
- *
- * We look for arguments in the following formats:
- *
- * - `--my-cli-argument value`
- * - `--my-cli-argument=value`
- * - `--myCliArgument value`
- * - `--myCliArgument=value`
- *
- * We also check for shortened aliases, which we define for a few arguments.
- *
- * @param args the CLI args we're dealing with
- * @param configCaseName the ConfigFlag key which we're looking to pull out a value for
- * @returns the value for the flag as well as the exact string which it matched from
- * the user input.
- */
- const getValue = (args, configCaseName) => {
- // for some CLI args we have a short alias, like 'c' for 'config'
- const alias = CLI_ARG_ALIASES[configCaseName];
- // we support supplying arguments in both dash-case and configCase
- // for ease of use
- const dashCaseName = toDashCase(configCaseName);
- let value;
- let matchingArg;
- args.forEach((arg, i) => {
- if (arg.startsWith(`--${dashCaseName}=`) || arg.startsWith(`--${configCaseName}=`)) {
- // our argument was passed at the command-line in the format --argName=arg-value
- [matchingArg, value] = parseEqualsArg(arg);
- }
- else if (arg === `--${dashCaseName}` || arg === `--${configCaseName}`) {
- // the next value in the array is assumed to be a value for this argument
- value = args[i + 1];
- matchingArg = arg;
- }
- else if (alias) {
- if (arg.startsWith(`-${alias}=`)) {
- [matchingArg, value] = parseEqualsArg(arg);
- }
- else if (arg === `-${alias}`) {
- value = args[i + 1];
- matchingArg = arg;
- }
- }
- });
- return { value, matchingArg };
- };
- /**
- * Parse an 'equals' argument, which is a CLI argument-value pair in the
- * format `--foobar=12` (as opposed to a space-separated format like
- * `--foobar 12`).
- *
- * To parse this we split on the `=`, returning the first part as the argument
- * name and the second part as the value. We join the value on `"="` in case
- * there is another `"="` in the argument.
- *
- * This function is safe to call with any arg, and can therefore be used as
- * an argument 'normalizer'. If CLI argument is not an 'equals' argument then
- * the return value will be a tuple of the original argument and an empty
- * string `""` for the value.
- *
- * In code terms, if you do:
- *
- * ```ts
- * const [arg, value] = parseEqualsArg("--myArgument")
- * ```
- *
- * Then `arg` will be `"--myArgument"` and `value` will be `""`, whereas if
- * you do:
- *
- *
- * ```ts
- * const [arg, value] = parseEqualsArg("--myArgument=myValue")
- * ```
- *
- * Then `arg` will be `"--myArgument"` and `value` will be `"myValue"`.
- *
- * @param arg the arg in question
- * @returns a tuple containing the arg name and the value (if present)
- */
- const parseEqualsArg = (arg) => {
- const [originalArg, ...value] = arg.split('=');
- return [originalArg, value.join('=')];
- };
- /**
- * Small helper for getting type-system-level assurance that a `string` can be
- * narrowed to a `LogLevel`
- *
- * @param maybeLogLevel the string to check
- * @returns whether this is a `LogLevel`
- */
- const isLogLevel = (maybeLogLevel) => readOnlyArrayHasStringMember(LOG_LEVELS, maybeLogLevel);
- const dependencies = [
- {
- name: "@stencil/core",
- version: "2.18.1",
- main: "compiler/stencil.js",
- resources: [
- "package.json",
- "compiler/lib.d.ts",
- "compiler/lib.dom.d.ts",
- "compiler/lib.dom.iterable.d.ts",
- "compiler/lib.es2015.collection.d.ts",
- "compiler/lib.es2015.core.d.ts",
- "compiler/lib.es2015.d.ts",
- "compiler/lib.es2015.generator.d.ts",
- "compiler/lib.es2015.iterable.d.ts",
- "compiler/lib.es2015.promise.d.ts",
- "compiler/lib.es2015.proxy.d.ts",
- "compiler/lib.es2015.reflect.d.ts",
- "compiler/lib.es2015.symbol.d.ts",
- "compiler/lib.es2015.symbol.wellknown.d.ts",
- "compiler/lib.es2016.array.include.d.ts",
- "compiler/lib.es2016.d.ts",
- "compiler/lib.es2016.full.d.ts",
- "compiler/lib.es2017.d.ts",
- "compiler/lib.es2017.full.d.ts",
- "compiler/lib.es2017.intl.d.ts",
- "compiler/lib.es2017.object.d.ts",
- "compiler/lib.es2017.sharedmemory.d.ts",
- "compiler/lib.es2017.string.d.ts",
- "compiler/lib.es2017.typedarrays.d.ts",
- "compiler/lib.es2018.asyncgenerator.d.ts",
- "compiler/lib.es2018.asynciterable.d.ts",
- "compiler/lib.es2018.d.ts",
- "compiler/lib.es2018.full.d.ts",
- "compiler/lib.es2018.intl.d.ts",
- "compiler/lib.es2018.promise.d.ts",
- "compiler/lib.es2018.regexp.d.ts",
- "compiler/lib.es2019.array.d.ts",
- "compiler/lib.es2019.d.ts",
- "compiler/lib.es2019.full.d.ts",
- "compiler/lib.es2019.object.d.ts",
- "compiler/lib.es2019.string.d.ts",
- "compiler/lib.es2019.symbol.d.ts",
- "compiler/lib.es2020.bigint.d.ts",
- "compiler/lib.es2020.d.ts",
- "compiler/lib.es2020.date.d.ts",
- "compiler/lib.es2020.full.d.ts",
- "compiler/lib.es2020.intl.d.ts",
- "compiler/lib.es2020.number.d.ts",
- "compiler/lib.es2020.promise.d.ts",
- "compiler/lib.es2020.sharedmemory.d.ts",
- "compiler/lib.es2020.string.d.ts",
- "compiler/lib.es2020.symbol.wellknown.d.ts",
- "compiler/lib.es2021.d.ts",
- "compiler/lib.es2021.full.d.ts",
- "compiler/lib.es2021.intl.d.ts",
- "compiler/lib.es2021.promise.d.ts",
- "compiler/lib.es2021.string.d.ts",
- "compiler/lib.es2021.weakref.d.ts",
- "compiler/lib.es2022.array.d.ts",
- "compiler/lib.es2022.d.ts",
- "compiler/lib.es2022.error.d.ts",
- "compiler/lib.es2022.full.d.ts",
- "compiler/lib.es2022.intl.d.ts",
- "compiler/lib.es2022.object.d.ts",
- "compiler/lib.es2022.string.d.ts",
- "compiler/lib.es5.d.ts",
- "compiler/lib.es6.d.ts",
- "compiler/lib.esnext.d.ts",
- "compiler/lib.esnext.full.d.ts",
- "compiler/lib.esnext.intl.d.ts",
- "compiler/lib.esnext.promise.d.ts",
- "compiler/lib.esnext.string.d.ts",
- "compiler/lib.esnext.weakref.d.ts",
- "compiler/lib.scripthost.d.ts",
- "compiler/lib.webworker.d.ts",
- "compiler/lib.webworker.importscripts.d.ts",
- "compiler/lib.webworker.iterable.d.ts",
- "internal/index.d.ts",
- "internal/index.js",
- "internal/package.json",
- "internal/stencil-ext-modules.d.ts",
- "internal/stencil-private.d.ts",
- "internal/stencil-public-compiler.d.ts",
- "internal/stencil-public-docs.d.ts",
- "internal/stencil-public-runtime.d.ts",
- "mock-doc/index.js",
- "mock-doc/package.json",
- "internal/client/css-shim.js",
- "internal/client/dom.js",
- "internal/client/index.js",
- "internal/client/package.json",
- "internal/client/patch-browser.js",
- "internal/client/patch-esm.js",
- "internal/client/shadow-css.js",
- "internal/hydrate/index.js",
- "internal/hydrate/package.json",
- "internal/hydrate/runner.js",
- "internal/hydrate/shadow-css.js",
- "internal/stencil-core/index.d.ts",
- "internal/stencil-core/index.js"
- ]
- },
- {
- name: "rollup",
- version: "2.42.3",
- main: "dist/es/rollup.browser.js"
- },
- {
- name: "terser",
- version: "5.6.1",
- main: "dist/bundle.min.js"
- },
- {
- name: "typescript",
- version: "4.7.4",
- main: "lib/typescript.js"
- }
- ];
- const IS_NODE_ENV = typeof global !== 'undefined' &&
- typeof require === 'function' &&
- !!global.process &&
- typeof __filename === 'string' &&
- (!global.origin || typeof global.origin !== 'string');
- const IS_BROWSER_ENV = typeof location !== 'undefined' && typeof navigator !== 'undefined' && typeof XMLHttpRequest !== 'undefined';
- /**
- * Creates an instance of a logger
- * @returns the new logger instance
- */
- const createLogger = () => {
- let useColors = IS_BROWSER_ENV;
- let level = 'info';
- return {
- enableColors: (uc) => (useColors = uc),
- getLevel: () => level,
- setLevel: (l) => (level = l),
- emoji: (e) => e,
- info: console.log.bind(console),
- warn: console.warn.bind(console),
- error: console.error.bind(console),
- debug: console.debug.bind(console),
- red: (msg) => msg,
- green: (msg) => msg,
- yellow: (msg) => msg,
- blue: (msg) => msg,
- magenta: (msg) => msg,
- cyan: (msg) => msg,
- gray: (msg) => msg,
- bold: (msg) => msg,
- dim: (msg) => msg,
- bgRed: (msg) => msg,
- createTimeSpan: (_startMsg, _debug = false) => ({
- duration: () => 0,
- finish: () => 0,
- }),
- printDiagnostics(diagnostics) {
- diagnostics.forEach((diagnostic) => logDiagnostic(diagnostic, useColors));
- },
- };
- };
- const logDiagnostic = (diagnostic, useColors) => {
- let color = BLUE;
- let prefix = 'Build';
- let msg = '';
- if (diagnostic.level === 'error') {
- color = RED;
- prefix = 'Error';
- }
- else if (diagnostic.level === 'warn') {
- color = YELLOW;
- prefix = 'Warning';
- }
- if (diagnostic.header) {
- prefix = diagnostic.header;
- }
- const filePath = diagnostic.relFilePath || diagnostic.absFilePath;
- if (filePath) {
- msg += filePath;
- if (typeof diagnostic.lineNumber === 'number' && diagnostic.lineNumber > 0) {
- msg += ', line ' + diagnostic.lineNumber;
- if (typeof diagnostic.columnNumber === 'number' && diagnostic.columnNumber > 0) {
- msg += ', column ' + diagnostic.columnNumber;
- }
- }
- msg += '\n';
- }
- msg += diagnostic.messageText;
- if (diagnostic.lines && diagnostic.lines.length > 0) {
- diagnostic.lines.forEach((l) => {
- msg += '\n' + l.lineNumber + ': ' + l.text;
- });
- msg += '\n';
- }
- if (useColors) {
- const styledPrefix = [
- '%c' + prefix,
- `background: ${color}; color: white; padding: 2px 3px; border-radius: 2px; font-size: 0.8em;`,
- ];
- console.log(...styledPrefix, msg);
- }
- else if (diagnostic.level === 'error') {
- console.error(msg);
- }
- else if (diagnostic.level === 'warn') {
- console.warn(msg);
- }
- else {
- console.log(msg);
- }
- };
- const YELLOW = `#f39c12`;
- const RED = `#c0392b`;
- const BLUE = `#3498db`;
- /**
- * Attempt to find a Stencil configuration file on the file system
- * @param opts the options needed to find the configuration file
- * @returns the results of attempting to find a configuration file on disk
- */
- const findConfig = async (opts) => {
- const sys = opts.sys;
- const cwd = sys.getCurrentDirectory();
- const rootDir = normalizePath(cwd);
- let configPath = opts.configPath;
- if (isString(configPath)) {
- if (!sys.platformPath.isAbsolute(configPath)) {
- // passed in a custom stencil config location,
- // but it's relative, so prefix the cwd
- configPath = normalizePath(sys.platformPath.join(cwd, configPath));
- }
- else {
- // config path already an absolute path, we're good here
- configPath = normalizePath(opts.configPath);
- }
- }
- else {
- // nothing was passed in, use the current working directory
- configPath = rootDir;
- }
- const results = {
- configPath,
- rootDir: normalizePath(cwd),
- diagnostics: [],
- };
- const stat = await sys.stat(configPath);
- if (stat.error) {
- const diagnostic = buildError(results.diagnostics);
- diagnostic.absFilePath = configPath;
- diagnostic.header = `Invalid config path`;
- diagnostic.messageText = `Config path "${configPath}" not found`;
- return results;
- }
- if (stat.isFile) {
- results.configPath = configPath;
- results.rootDir = sys.platformPath.dirname(configPath);
- }
- else if (stat.isDirectory) {
- // this is only a directory, so let's make some assumptions
- for (const configName of ['stencil.config.ts', 'stencil.config.js']) {
- const testConfigFilePath = sys.platformPath.join(configPath, configName);
- const stat = await sys.stat(testConfigFilePath);
- if (stat.isFile) {
- results.configPath = testConfigFilePath;
- results.rootDir = sys.platformPath.dirname(testConfigFilePath);
- break;
- }
- }
- }
- return results;
- };
- const loadCoreCompiler = async (sys) => {
- await sys.dynamicImport(sys.getCompilerExecutingPath());
- return globalThis.stencil;
- };
- /**
- * Log the name of this package (`@stencil/core`) to an output stream
- *
- * The output stream is determined by the {@link Logger} instance that is provided as an argument to this function
- *
- * The name of the package may not be logged, by design, for certain `task` types and logging levels
- *
- * @param logger the logging entity to use to output the name of the package
- * @param task the current task
- */
- const startupLog = (logger, task) => {
- if (task === 'info' || task === 'serve' || task === 'version') {
- return;
- }
- logger.info(logger.cyan(`@stencil/core`));
- };
- /**
- * Log this package's version to an output stream
- *
- * The output stream is determined by the {@link Logger} instance that is provided as an argument to this function
- *
- * The package version may not be logged, by design, for certain `task` types and logging levels
- *
- * @param logger the logging entity to use for output
- * @param task the current task
- * @param coreCompiler the compiler instance to derive version information from
- */
- const startupLogVersion = (logger, task, coreCompiler) => {
- if (task === 'info' || task === 'serve' || task === 'version') {
- return;
- }
- const isDevBuild = coreCompiler.version.includes('-dev.');
- let startupMsg;
- if (isDevBuild) {
- startupMsg = logger.yellow('[LOCAL DEV]');
- }
- else {
- startupMsg = logger.cyan(`v${coreCompiler.version}`);
- }
- startupMsg += logger.emoji(' ' + coreCompiler.vermoji);
- logger.info(startupMsg);
- };
- /**
- * Log details from a {@link CompilerSystem} used by Stencil to an output stream
- *
- * The output stream is determined by the {@link Logger} instance that is provided as an argument to this function
- *
- * @param sys the `CompilerSystem` to report details on
- * @param logger the logging entity to use for output
- * @param flags user set flags for the current invocation of Stencil
- * @param coreCompiler the compiler instance being used for this invocation of Stencil
- */
- const loadedCompilerLog = (sys, logger, flags, coreCompiler) => {
- const sysDetails = sys.details;
- const runtimeInfo = `${sys.name} ${sys.version}`;
- const platformInfo = sysDetails
- ? `${sysDetails.platform}, ${sysDetails.cpuModel}`
- : `Unknown Platform, Unknown CPU Model`;
- const statsInfo = sysDetails
- ? `cpus: ${sys.hardwareConcurrency}, freemem: ${Math.round(sysDetails.freemem() / 1000000)}MB, totalmem: ${Math.round(sysDetails.totalmem / 1000000)}MB`
- : 'Unknown CPU Core Count, Unknown Memory';
- if (logger.getLevel() === 'debug') {
- logger.debug(runtimeInfo);
- logger.debug(platformInfo);
- logger.debug(statsInfo);
- logger.debug(`compiler: ${sys.getCompilerExecutingPath()}`);
- logger.debug(`build: ${coreCompiler.buildId}`);
- }
- else if (flags.ci) {
- logger.info(runtimeInfo);
- logger.info(platformInfo);
- logger.info(statsInfo);
- }
- };
- /**
- * Log various warnings to an output stream
- *
- * The output stream is determined by the {@link Logger} instance attached to the `config` argument to this function
- *
- * @param coreCompiler the compiler instance being used for this invocation of Stencil
- * @param config a validated configuration object to be used for this run of Stencil
- */
- const startupCompilerLog = (coreCompiler, config) => {
- if (config.suppressLogs === true) {
- return;
- }
- const { logger } = config;
- const isDebug = logger.getLevel() === 'debug';
- const isPrerelease = coreCompiler.version.includes('-');
- const isDevBuild = coreCompiler.version.includes('-dev.');
- if (isPrerelease && !isDevBuild) {
- logger.warn(logger.yellow(`This is a prerelease build, undocumented changes might happen at any time. Technical support is not available for prereleases, but any assistance testing is appreciated.`));
- }
- if (config.devMode && !isDebug) {
- if (config.buildEs5) {
- logger.warn(`Generating ES5 during development is a very task expensive, initial and incremental builds will be much slower. Drop the '--es5' flag and use a modern browser for development.`);
- }
- if (!config.enableCache) {
- logger.warn(`Disabling cache during development will slow down incremental builds.`);
- }
- }
- };
- const startCheckVersion = async (config, currentVersion) => {
- if (config.devMode && !config.flags.ci && !currentVersion.includes('-dev.') && isFunction(config.sys.checkVersion)) {
- return config.sys.checkVersion(config.logger, currentVersion);
- }
- return null;
- };
- const printCheckVersionResults = async (versionChecker) => {
- if (versionChecker) {
- const checkVersionResults = await versionChecker;
- if (isFunction(checkVersionResults)) {
- checkVersionResults();
- }
- }
- };
- const taskPrerender = async (coreCompiler, config) => {
- startupCompilerLog(coreCompiler, config);
- const hydrateAppFilePath = config.flags.unknownArgs[0];
- if (typeof hydrateAppFilePath !== 'string') {
- config.logger.error(`Missing hydrate app script path`);
- return config.sys.exit(1);
- }
- const srcIndexHtmlPath = config.srcIndexHtml;
- const diagnostics = await runPrerenderTask(coreCompiler, config, hydrateAppFilePath, null, srcIndexHtmlPath);
- config.logger.printDiagnostics(diagnostics);
- if (diagnostics.some((d) => d.level === 'error')) {
- return config.sys.exit(1);
- }
- };
- const runPrerenderTask = async (coreCompiler, config, hydrateAppFilePath, componentGraph, srcIndexHtmlPath) => {
- const diagnostics = [];
- try {
- const prerenderer = await coreCompiler.createPrerenderer(config);
- const results = await prerenderer.start({
- hydrateAppFilePath,
- componentGraph,
- srcIndexHtmlPath,
- });
- diagnostics.push(...results.diagnostics);
- }
- catch (e) {
- catchError(diagnostics, e);
- }
- return diagnostics;
- };
- const taskWatch = async (coreCompiler, config) => {
- let devServer = null;
- let exitCode = 0;
- try {
- startupCompilerLog(coreCompiler, config);
- const versionChecker = startCheckVersion(config, coreCompiler.version);
- const compiler = await coreCompiler.createCompiler(config);
- const watcher = await compiler.createWatcher();
- if (config.flags.serve) {
- const devServerPath = config.sys.getDevServerExecutingPath();
- const { start } = await config.sys.dynamicImport(devServerPath);
- devServer = await start(config.devServer, config.logger, watcher);
- }
- config.sys.onProcessInterrupt(() => {
- config.logger.debug(`close watch`);
- compiler && compiler.destroy();
- });
- const rmVersionCheckerLog = watcher.on('buildFinish', async () => {
- // log the version check one time
- rmVersionCheckerLog();
- printCheckVersionResults(versionChecker);
- });
- if (devServer) {
- const rmDevServerLog = watcher.on('buildFinish', () => {
- // log the dev server url one time
- rmDevServerLog();
- config.logger.info(`${config.logger.cyan(devServer.browserUrl)}\n`);
- });
- }
- const closeResults = await watcher.start();
- if (closeResults.exitCode > 0) {
- exitCode = closeResults.exitCode;
- }
- }
- catch (e) {
- exitCode = 1;
- config.logger.error(e);
- }
- if (devServer) {
- await devServer.close();
- }
- if (exitCode > 0) {
- return config.sys.exit(exitCode);
- }
- };
- const isOutputTargetHydrate = (o) => o.type === DIST_HYDRATE_SCRIPT;
- const isOutputTargetDocs = (o) => o.type === DOCS_README || o.type === DOCS_JSON || o.type === DOCS_CUSTOM || o.type === DOCS_VSCODE;
- const DIST_HYDRATE_SCRIPT = 'dist-hydrate-script';
- const DOCS_CUSTOM = 'docs-custom';
- const DOCS_JSON = 'docs-json';
- const DOCS_README = 'docs-readme';
- const DOCS_VSCODE = 'docs-vscode';
- const WWW = 'www';
- const tryFn = async (fn, ...args) => {
- try {
- return await fn(...args);
- }
- catch (_a) {
- // ignore
- }
- return null;
- };
- const isInteractive = (sys, flags, object) => {
- const terminalInfo = object ||
- Object.freeze({
- tty: sys.isTTY() ? true : false,
- ci: ['CI', 'BUILD_ID', 'BUILD_NUMBER', 'BITBUCKET_COMMIT', 'CODEBUILD_BUILD_ARN'].filter((v) => { var _a; return !!((_a = sys.getEnvironmentVar) === null || _a === void 0 ? void 0 : _a.call(sys, v)); }).length > 0 || !!flags.ci,
- });
- return terminalInfo.tty && !terminalInfo.ci;
- };
- const UUID_REGEX = new RegExp(/^[0-9A-F]{8}-[0-9A-F]{4}-4[0-9A-F]{3}-[89AB][0-9A-F]{3}-[0-9A-F]{12}$/i);
- // Plucked from https://github.com/ionic-team/capacitor/blob/b893a57aaaf3a16e13db9c33037a12f1a5ac92e0/cli/src/util/uuid.ts
- function uuidv4() {
- return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => {
- const r = (Math.random() * 16) | 0;
- const v = c == 'x' ? r : (r & 0x3) | 0x8;
- return v.toString(16);
- });
- }
- /**
- * Reads and parses a JSON file from the given `path`
- * @param sys The system where the command is invoked
- * @param path the path on the file system to read and parse
- * @returns the parsed JSON
- */
- async function readJson(sys, path) {
- const file = await sys.readFile(path);
- return !!file && JSON.parse(file);
- }
- /**
- * Does the command have the debug flag?
- * @param flags The configuration flags passed into the Stencil command
- * @returns true if --debug has been passed, otherwise false
- */
- function hasDebug(flags) {
- return !!flags.debug;
- }
- /**
- * Does the command have the verbose and debug flags?
- * @param flags The configuration flags passed into the Stencil command
- * @returns true if both --debug and --verbose have been passed, otherwise false
- */
- function hasVerbose(flags) {
- return !!flags.verbose && hasDebug(flags);
- }
- const isTest$1 = () => process.env.JEST_WORKER_ID !== undefined;
- const defaultConfig = (sys) => sys.resolvePath(`${sys.homeDir()}/.ionic/${isTest$1() ? 'tmp-config.json' : 'config.json'}`);
- const defaultConfigDirectory = (sys) => sys.resolvePath(`${sys.homeDir()}/.ionic`);
- /**
- * Reads an Ionic configuration file from disk, parses it, and performs any necessary corrections to it if certain
- * values are deemed to be malformed
- * @param sys The system where the command is invoked
- * @returns the config read from disk that has been potentially been updated
- */
- async function readConfig(sys) {
- let config = await readJson(sys, defaultConfig(sys));
- if (!config) {
- config = {
- 'tokens.telemetry': uuidv4(),
- 'telemetry.stencil': true,
- };
- await writeConfig(sys, config);
- }
- else if (!config['tokens.telemetry'] || !UUID_REGEX.test(config['tokens.telemetry'])) {
- const newUuid = uuidv4();
- await writeConfig(sys, { ...config, 'tokens.telemetry': newUuid });
- config['tokens.telemetry'] = newUuid;
- }
- return config;
- }
- /**
- * Writes an Ionic configuration file to disk.
- * @param sys The system where the command is invoked
- * @param config The config passed into the Stencil command
- * @returns boolean If the command was successful
- */
- async function writeConfig(sys, config) {
- let result = false;
- try {
- await sys.createDir(defaultConfigDirectory(sys), { recursive: true });
- await sys.writeFile(defaultConfig(sys), JSON.stringify(config, null, 2));
- result = true;
- }
- catch (error) {
- console.error(`Stencil Telemetry: couldn't write configuration file to ${defaultConfig(sys)} - ${error}.`);
- }
- return result;
- }
- /**
- * Update a subset of the Ionic config.
- * @param sys The system where the command is invoked
- * @param newOptions The new options to save
- * @returns boolean If the command was successful
- */
- async function updateConfig(sys, newOptions) {
- const config = await readConfig(sys);
- return await writeConfig(sys, Object.assign(config, newOptions));
- }
- /**
- * Used to determine if tracking should occur.
- * @param config The config passed into the Stencil command
- * @param sys The system where the command is invoked
- * @param ci whether or not the process is running in a Continuous Integration (CI) environment
- * @returns true if telemetry should be sent, false otherwise
- */
- async function shouldTrack(config, sys, ci) {
- return !ci && isInteractive(sys, config.flags) && (await checkTelemetry(sys));
- }
- /**
- * Used to within taskBuild to provide the component_count property.
- *
- * @param sys The system where the command is invoked
- * @param config The config passed into the Stencil command
- * @param coreCompiler The compiler used to do builds
- * @param result The results of a compiler build.
- */
- async function telemetryBuildFinishedAction(sys, config, coreCompiler, result) {
- const tracking = await shouldTrack(config, sys, !!config.flags.ci);
- if (!tracking) {
- return;
- }
- const component_count = result.componentGraph ? Object.keys(result.componentGraph).length : undefined;
- const data = await prepareData(coreCompiler, config, sys, result.duration, component_count);
- await sendMetric(sys, config, 'stencil_cli_command', data);
- config.logger.debug(`${config.logger.blue('Telemetry')}: ${config.logger.gray(JSON.stringify(data))}`);
- }
- /**
- * A function to wrap a compiler task function around. Will send telemetry if, and only if, the machine allows.
- *
- * @param sys The system where the command is invoked
- * @param config The config passed into the Stencil command
- * @param coreCompiler The compiler used to do builds
- * @param action A Promise-based function to call in order to get the duration of any given command.
- * @returns void
- */
- async function telemetryAction(sys, config, coreCompiler, action) {
- const tracking = await shouldTrack(config, sys, !!config.flags.ci);
- let duration = undefined;
- let error;
- if (action) {
- const start = new Date();
- try {
- await action();
- }
- catch (e) {
- error = e;
- }
- const end = new Date();
- duration = end.getTime() - start.getTime();
- }
- // We'll get componentCount details inside the taskBuild, so let's not send two messages.
- if (!tracking || (config.flags.task == 'build' && !config.flags.args.includes('--watch'))) {
- return;
- }
- const data = await prepareData(coreCompiler, config, sys, duration);
- await sendMetric(sys, config, 'stencil_cli_command', data);
- config.logger.debug(`${config.logger.blue('Telemetry')}: ${config.logger.gray(JSON.stringify(data))}`);
- if (error) {
- throw error;
- }
- }
- /**
- * Helper function to determine if a Stencil configuration builds an application.
- *
- * This function is a rough approximation whether an application is generated as a part of a Stencil build, based on
- * contents of the project's `stencil.config.ts` file.
- *
- * @param config the configuration used by the Stencil project
- * @returns true if we believe the project generates an application, false otherwise
- */
- function hasAppTarget(config) {
- return config.outputTargets.some((target) => target.type === WWW && (!!target.serviceWorker || (!!target.baseUrl && target.baseUrl !== '/')));
- }
- function isUsingYarn(sys) {
- var _a;
- return ((_a = sys.getEnvironmentVar('npm_execpath')) === null || _a === void 0 ? void 0 : _a.includes('yarn')) || false;
- }
- /**
- * Build a list of the different types of output targets used in a Stencil configuration.
- *
- * Duplicate entries will not be returned from the list
- *
- * @param config the configuration used by the Stencil project
- * @returns a unique list of output target types found in the Stencil configuration
- */
- function getActiveTargets(config) {
- const result = config.outputTargets.map((t) => t.type);
- return Array.from(new Set(result));
- }
- /**
- * Prepare data for telemetry
- *
- * @param coreCompiler the core compiler
- * @param config the current Stencil config
- * @param sys the compiler system instance in use
- * @param duration_ms the duration of the action being tracked
- * @param component_count the number of components being built (optional)
- * @returns a Promise wrapping data for the telemetry endpoint
- */
- const prepareData = async (coreCompiler, config, sys, duration_ms, component_count = undefined) => {
- var _a, _b, _c;
- const { typescript, rollup } = coreCompiler.versions || { typescript: 'unknown', rollup: 'unknown' };
- const { packages, packagesNoVersions } = await getInstalledPackages(sys, config);
- const targets = getActiveTargets(config);
- const yarn = isUsingYarn(sys);
- const stencil = coreCompiler.version || 'unknown';
- const system = `${sys.name} ${sys.version}`;
- const os_name = (_a = sys.details) === null || _a === void 0 ? void 0 : _a.platform;
- const os_version = (_b = sys.details) === null || _b === void 0 ? void 0 : _b.release;
- const cpu_model = (_c = sys.details) === null || _c === void 0 ? void 0 : _c.cpuModel;
- const build = coreCompiler.buildId || 'unknown';
- const has_app_pwa_config = hasAppTarget(config);
- const anonymizedConfig = anonymizeConfigForTelemetry(config);
- return {
- yarn,
- duration_ms,
- component_count,
- targets,
- packages,
- packages_no_versions: packagesNoVersions,
- arguments: config.flags.args,
- task: config.flags.task,
- stencil,
- system,
- system_major: getMajorVersion(system),
- os_name,
- os_version,
- cpu_model,
- build,
- typescript,
- rollup,
- has_app_pwa_config,
- config: anonymizedConfig,
- };
- };
- // props in output targets for which we retain their original values when
- // preparing a config for telemetry
- //
- // we omit the values of all other fields on output targets.
- const OUTPUT_TARGET_KEYS_TO_KEEP = ['type'];
- // top-level config props that we anonymize for telemetry
- const CONFIG_PROPS_TO_ANONYMIZE = [
- 'rootDir',
- 'fsNamespace',
- 'packageJsonFilePath',
- 'namespace',
- 'srcDir',
- 'srcIndexHtml',
- 'buildLogFilePath',
- 'cacheDir',
- 'configPath',
- 'tsconfig',
- ];
- // Props we delete entirely from the config for telemetry
- //
- // TODO(STENCIL-469): Investigate improving anonymization for tsCompilerOptions and devServer
- const CONFIG_PROPS_TO_DELETE = ['sys', 'logger', 'tsCompilerOptions', 'devServer'];
- /**
- * Anonymize the config for telemetry, replacing potentially revealing config props
- * with a placeholder string if they are present (this lets us still track how frequently
- * these config options are being used)
- *
- * @param config the config to anonymize
- * @returns an anonymized copy of the same config
- */
- const anonymizeConfigForTelemetry = (config) => {
- const anonymizedConfig = { ...config };
- for (const prop of CONFIG_PROPS_TO_ANONYMIZE) {
- if (anonymizedConfig[prop] !== undefined) {
- anonymizedConfig[prop] = 'omitted';
- }
- }
- anonymizedConfig.outputTargets = config.outputTargets.map((target) => {
- // Anonymize the outputTargets on our configuration, taking advantage of the
- // optional 2nd argument to `JSON.stringify`. If anything is not a string
- // we retain it so that any nested properties are handled, else we check
- // whether it's in our 'keep' list to decide whether to keep it or replace it
- // with `"omitted"`.
- const anonymizedOT = JSON.parse(JSON.stringify(target, (key, value) => {
- if (!(typeof value === 'string')) {
- return value;
- }
- if (OUTPUT_TARGET_KEYS_TO_KEEP.includes(key)) {
- return value;
- }
- return 'omitted';
- }));
- // this prop has to be handled separately because it is an array
- // so the replace function above will be called with all of its
- // members, giving us `["omitted", "omitted", ...]`.
- //
- // Instead, we check for its presence and manually copy over.
- if (isOutputTargetHydrate(target) && target.external) {
- anonymizedOT['external'] = target.external.concat();
- }
- return anonymizedOT;
- });
- // TODO(STENCIL-469): Investigate improving anonymization for tsCompilerOptions and devServer
- for (const prop of CONFIG_PROPS_TO_DELETE) {
- delete anonymizedConfig[prop];
- }
- return anonymizedConfig;
- };
- /**
- * Reads package-lock.json, yarn.lock, and package.json files in order to cross-reference
- * the dependencies and devDependencies properties. Pulls up the current installed version
- * of each package under the @stencil, @ionic, and @capacitor scopes.
- *
- * @param sys the system instance where telemetry is invoked
- * @param config the Stencil configuration associated with the current task that triggered telemetry
- * @returns an object listing all dev and production dependencies under the aforementioned scopes
- */
- async function getInstalledPackages(sys, config) {
- let packages = [];
- let packagesNoVersions = [];
- const yarn = isUsingYarn(sys);
- try {
- // Read package.json and package-lock.json
- const appRootDir = sys.getCurrentDirectory();
- const packageJson = await tryFn(readJson, sys, sys.resolvePath(appRootDir + '/package.json'));
- // They don't have a package.json for some reason? Eject button.
- if (!packageJson) {
- return { packages, packagesNoVersions };
- }
- const rawPackages = Object.entries({
- ...packageJson.devDependencies,
- ...packageJson.dependencies,
- });
- // Collect packages only in the stencil, ionic, or capacitor org's:
- // https://www.npmjs.com/org/stencil
- const ionicPackages = rawPackages.filter(([k]) => k.startsWith('@stencil/') || k.startsWith('@ionic/') || k.startsWith('@capacitor/'));
- try {
- packages = yarn ? await yarnPackages(sys, ionicPackages) : await npmPackages(sys, ionicPackages);
- }
- catch (e) {
- packages = ionicPackages.map(([k, v]) => `${k}@${v.replace('^', '')}`);
- }
- packagesNoVersions = ionicPackages.map(([k]) => `${k}`);
- return { packages, packagesNoVersions };
- }
- catch (err) {
- hasDebug(config.flags) && console.error(err);
- return { packages, packagesNoVersions };
- }
- }
- /**
- * Visits the npm lock file to find the exact versions that are installed
- * @param sys The system where the command is invoked
- * @param ionicPackages a list of the found packages matching `@stencil`, `@capacitor`, or `@ionic` from the package.json file.
- * @returns an array of strings of all the packages and their versions.
- */
- async function npmPackages(sys, ionicPackages) {
- const appRootDir = sys.getCurrentDirectory();
- const packageLockJson = await tryFn(readJson, sys, sys.resolvePath(appRootDir + '/package-lock.json'));
- return ionicPackages.map(([k, v]) => {
- var _a, _b, _c, _d;
- let version = (_d = (_b = (_a = packageLockJson === null || packageLockJson === void 0 ? void 0 : packageLockJson.dependencies[k]) === null || _a === void 0 ? void 0 : _a.version) !== null && _b !== void 0 ? _b : (_c = packageLockJson === null || packageLockJson === void 0 ? void 0 : packageLockJson.devDependencies[k]) === null || _c === void 0 ? void 0 : _c.version) !== null && _d !== void 0 ? _d : v;
- version = version.includes('file:') ? sanitizeDeclaredVersion(v) : version;
- return `${k}@${version}`;
- });
- }
- /**
- * Visits the yarn lock file to find the exact versions that are installed
- * @param sys The system where the command is invoked
- * @param ionicPackages a list of the found packages matching `@stencil`, `@capacitor`, or `@ionic` from the package.json file.
- * @returns an array of strings of all the packages and their versions.
- */
- async function yarnPackages(sys, ionicPackages) {
- const appRootDir = sys.getCurrentDirectory();
- const yarnLock = sys.readFileSync(sys.resolvePath(appRootDir + '/yarn.lock'));
- const yarnLockYml = sys.parseYarnLockFile(yarnLock);
- return ionicPackages.map(([k, v]) => {
- var _a;
- const identifiedVersion = `${k}@${v}`;
- let version = (_a = yarnLockYml.object[identifiedVersion]) === null || _a === void 0 ? void 0 : _a.version;
- version = version.includes('undefined') ? sanitizeDeclaredVersion(identifiedVersion) : version;
- return `${k}@${version}`;
- });
- }
- /**
- * This function is used for fallback purposes, where an npm or yarn lock file doesn't exist in the consumers directory.
- * This will strip away '*', '^' and '~' from the declared package versions in a package.json.
- * @param version the raw semver pattern identifier version string
- * @returns a cleaned up representation without any qualifiers
- */
- function sanitizeDeclaredVersion(version) {
- return version.replace(/[*^~]/g, '');
- }
- /**
- * If telemetry is enabled, send a metric to an external data store
- *
- * @param sys the system instance where telemetry is invoked
- * @param config the Stencil configuration associated with the current task that triggered telemetry
- * @param name the name of a trackable metric. Note this name is not necessarily a scalar value to track, like
- * "Stencil Version". For example, "stencil_cli_command" is a name that is used to track all CLI command information.
- * @param value the data to send to the external data store under the provided name argument
- */
- async function sendMetric(sys, config, name, value) {
- const session_id = await getTelemetryToken(sys);
- const message = {
- name,
- timestamp: new Date().toISOString(),
- source: 'stencil_cli',
- value,
- session_id,
- };
- await sendTelemetry(sys, config, message);
- }
- /**
- * Used to read the config file's tokens.telemetry property.
- *
- * @param sys The system where the command is invoked
- * @returns string
- */
- async function getTelemetryToken(sys) {
- const config = await readConfig(sys);
- if (config['tokens.telemetry'] === undefined) {
- config['tokens.telemetry'] = uuidv4();
- await writeConfig(sys, config);
- }
- return config['tokens.telemetry'];
- }
- /**
- * Issues a request to the telemetry server.
- * @param sys The system where the command is invoked
- * @param config The config passed into the Stencil command
- * @param data Data to be tracked
- */
- async function sendTelemetry(sys, config, data) {
- try {
- const now = new Date().toISOString();
- const body = {
- metrics: [data],
- sent_at: now,
- };
- // This request is only made if telemetry is on.
- const response = await sys.fetch('https://api.ionicjs.com/events/metrics', {
- method: 'POST',
- headers: {
- 'Content-Type': 'application/json',
- },
- body: JSON.stringify(body),
- });
- hasVerbose(config.flags) &&
- console.debug('\nSent %O metric to events service (status: %O)', data.name, response.status, '\n');
- if (response.status !== 204) {
- hasVerbose(config.flags) &&
- console.debug('\nBad response from events service. Request body: %O', response.body.toString(), '\n');
- }
- }
- catch (e) {
- hasVerbose(config.flags) && console.debug('Telemetry request failed:', e);
- }
- }
- /**
- * Checks if telemetry is enabled on this machine
- * @param sys The system where the command is invoked
- * @returns true if telemetry is enabled, false otherwise
- */
- async function checkTelemetry(sys) {
- const config = await readConfig(sys);
- if (config['telemetry.stencil'] === undefined) {
- config['telemetry.stencil'] = true;
- await writeConfig(sys, config);
- }
- return config['telemetry.stencil'];
- }
- /**
- * Writes to the config file, enabling telemetry for this machine.
- * @param sys The system where the command is invoked
- * @returns true if writing the file was successful, false otherwise
- */
- async function enableTelemetry(sys) {
- return await updateConfig(sys, { 'telemetry.stencil': true });
- }
- /**
- * Writes to the config file, disabling telemetry for this machine.
- * @param sys The system where the command is invoked
- * @returns true if writing the file was successful, false otherwise
- */
- async function disableTelemetry(sys) {
- return await updateConfig(sys, { 'telemetry.stencil': false });
- }
- /**
- * Takes in a semver string in order to return the major version.
- * @param version The fully qualified semver version
- * @returns a string of the major version
- */
- function getMajorVersion(version) {
- const parts = version.split('.');
- return parts[0];
- }
- const taskBuild = async (coreCompiler, config) => {
- if (config.flags.watch) {
- // watch build
- await taskWatch(coreCompiler, config);
- return;
- }
- // one-time build
- let exitCode = 0;
- try {
- startupCompilerLog(coreCompiler, config);
- const versionChecker = startCheckVersion(config, coreCompiler.version);
- const compiler = await coreCompiler.createCompiler(config);
- const results = await compiler.build();
- await telemetryBuildFinishedAction(config.sys, config, coreCompiler, results);
- await compiler.destroy();
- if (results.hasError) {
- exitCode = 1;
- }
- else if (config.flags.prerender) {
- const prerenderDiagnostics = await runPrerenderTask(coreCompiler, config, results.hydrateAppFilePath, results.componentGraph, null);
- config.logger.printDiagnostics(prerenderDiagnostics);
- if (prerenderDiagnostics.some((d) => d.level === 'error')) {
- exitCode = 1;
- }
- }
- await printCheckVersionResults(versionChecker);
- }
- catch (e) {
- exitCode = 1;
- config.logger.error(e);
- }
- if (exitCode > 0) {
- return config.sys.exit(exitCode);
- }
- };
- const taskDocs = async (coreCompiler, config) => {
- config.devServer = null;
- config.outputTargets = config.outputTargets.filter(isOutputTargetDocs);
- config.devMode = true;
- startupCompilerLog(coreCompiler, config);
- const compiler = await coreCompiler.createCompiler(config);
- await compiler.build();
- await compiler.destroy();
- };
- /**
- * Task to generate component boilerplate and write it to disk. This task can
- * cause the program to exit with an error under various circumstances, such as
- * being called in an inappropriate place, being asked to overwrite files that
- * already exist, etc.
- *
- * @param coreCompiler the CoreCompiler we're using currently, here we're
- * mainly accessing the `path` module
- * @param config the user-supplied config, which we need here to access `.sys`.
- */
- const taskGenerate = async (coreCompiler, config) => {
- if (!IS_NODE_ENV) {
- config.logger.error(`"generate" command is currently only implemented for a NodeJS environment`);
- return config.sys.exit(1);
- }
- const path = coreCompiler.path;
- if (!config.configPath) {
- config.logger.error('Please run this command in your root directory (i. e. the one containing stencil.config.ts).');
- return config.sys.exit(1);
- }
- const absoluteSrcDir = config.srcDir;
- if (!absoluteSrcDir) {
- config.logger.error(`Stencil's srcDir was not specified.`);
- return config.sys.exit(1);
- }
- const { prompt } = await Promise.resolve().then(function () { return /*#__PURE__*/_interopNamespace(require('../sys/node/prompts.js')); });
- const input = config.flags.unknownArgs.find((arg) => !arg.startsWith('-')) ||
- (await prompt({ name: 'tagName', type: 'text', message: 'Component tag name (dash-case):' })).tagName;
- const { dir, base: componentName } = path.parse(input);
- const tagError = validateComponentTag(componentName);
- if (tagError) {
- config.logger.error(tagError);
- return config.sys.exit(1);
- }
- const extensionsToGenerate = ['tsx', ...(await chooseFilesToGenerate())];
- const testFolder = extensionsToGenerate.some(isTest) ? 'test' : '';
- const outDir = path.join(absoluteSrcDir, 'components', dir, componentName);
- await config.sys.createDir(path.join(outDir, testFolder), { recursive: true });
- const filesToGenerate = extensionsToGenerate.map((extension) => ({
- extension,
- path: getFilepathForFile(coreCompiler, outDir, componentName, extension),
- }));
- await checkForOverwrite(filesToGenerate, config);
- const writtenFiles = await Promise.all(filesToGenerate.map((file) => getBoilerplateAndWriteFile(config, componentName, extensionsToGenerate.includes('css'), file))).catch((error) => config.logger.error(error));
- if (!writtenFiles) {
- return config.sys.exit(1);
- }
- // We use `console.log` here rather than our `config.logger` because we don't want
- // our TUI messages to be prefixed with timestamps and so on.
- //
- // See STENCIL-424 for details.
- console.log();
- console.log(`${config.logger.gray('$')} stencil generate ${input}`);
- console.log();
- console.log(config.logger.bold('The following files have been generated:'));
- const absoluteRootDir = config.rootDir;
- writtenFiles.map((file) => console.log(` - ${path.relative(absoluteRootDir, file)}`));
- };
- /**
- * Show a checkbox prompt to select the files to be generated.
- *
- * @returns a read-only array of `GenerableExtension`, the extensions that the user has decided
- * to generate
- */
- const chooseFilesToGenerate = async () => {
- const { prompt } = await Promise.resolve().then(function () { return /*#__PURE__*/_interopNamespace(require('../sys/node/prompts.js')); });
- return (await prompt({
- name: 'filesToGenerate',
- type: 'multiselect',
- message: 'Which additional files do you want to generate?',
- choices: [
- { value: 'css', title: 'Stylesheet (.css)', selected: true },
- { value: 'spec.tsx', title: 'Spec Test (.spec.tsx)', selected: true },
- { value: 'e2e.ts', title: 'E2E Test (.e2e.ts)', selected: true },
- ],
- })).filesToGenerate;
- };
- /**
- * Get a filepath for a file we want to generate!
- *
- * The filepath for a given file depends on the path, the user-supplied
- * component name, the extension, and whether we're inside of a test directory.
- *
- * @param coreCompiler the compiler we're using, here to acces the `.path` module
- * @param path path to where we're going to generate the component
- * @param componentName the user-supplied name for the generated component
- * @param extension the file extension
- * @returns the full filepath to the component (with a possible `test` directory
- * added)
- */
- const getFilepathForFile = (coreCompiler, path, componentName, extension) => isTest(extension)
- ? coreCompiler.path.join(path, 'test', `${componentName}.${extension}`)
- : coreCompiler.path.join(path, `${componentName}.${extension}`);
- /**
- * Get the boilerplate for a file and write it to disk
- *
- * @param config the current config, needed for file operations
- * @param componentName the component name (user-supplied)
- * @param withCss are we generating CSS?
- * @param file the file we want to write
- * @returns a `Promise<string>` which holds the full filepath we've written to,
- * used to print out a little summary of our activity to the user.
- */
- const getBoilerplateAndWriteFile = async (config, componentName, withCss, file) => {
- const boilerplate = getBoilerplateByExtension(componentName, file.extension, withCss);
- await config.sys.writeFile(file.path, boilerplate);
- return file.path;
- };
- /**
- * Check to see if any of the files we plan to write already exist and would
- * therefore be overwritten if we proceed, because we'd like to not overwrite
- * people's code!
- *
- * This function will check all the filepaths and if it finds any files log an
- * error and exit with an error code. If it doesn't find anything it will just
- * peacefully return `Promise<void>`.
- *
- * @param files the files we want to check
- * @param config the Config object, used here to get access to `sys.readFile`
- */
- const checkForOverwrite = async (files, config) => {
- const alreadyPresent = [];
- await Promise.all(files.map(async ({ path }) => {
- if ((await config.sys.readFile(path)) !== undefined) {
- alreadyPresent.push(path);
- }
- }));
- if (alreadyPresent.length > 0) {
- config.logger.error('Generating code would overwrite the following files:', ...alreadyPresent.map((path) => '\t' + path));
- await config.sys.exit(1);
- }
- };
- /**
- * Check if an extension is for a test
- *
- * @param extension the extension we want to check
- * @returns a boolean indicating whether or not its a test
- */
- const isTest = (extension) => {
- return extension === 'e2e.ts' || extension === 'spec.tsx';
- };
- /**
- * Get the boilerplate for a file by its extension.
- *
- * @param tagName the name of the component we're generating
- * @param extension the file extension we want boilerplate for (.css, tsx, etc)
- * @param withCss a boolean indicating whether we're generating a CSS file
- * @returns a string container the file boilerplate for the supplied extension
- */
- const getBoilerplateByExtension = (tagName, extension, withCss) => {
- switch (extension) {
- case 'tsx':
- return getComponentBoilerplate(tagName, withCss);
- case 'css':
- return getStyleUrlBoilerplate();
- case 'spec.tsx':
- return getSpecTestBoilerplate(tagName);
- case 'e2e.ts':
- return getE2eTestBoilerplate(tagName);
- default:
- throw new Error(`Unkown extension "${extension}".`);
- }
- };
- /**
- * Get the boilerplate for a file containing the definition of a component
- * @param tagName the name of the tag to give the component
- * @param hasStyle designates if the component has an external stylesheet or not
- * @returns the contents of a file that defines a component
- */
- const getComponentBoilerplate = (tagName, hasStyle) => {
- const decorator = [`{`];
- decorator.push(` tag: '${tagName}',`);
- if (hasStyle) {
- decorator.push(` styleUrl: '${tagName}.css',`);
- }
- decorator.push(` shadow: true,`);
- decorator.push(`}`);
- return `import { Component, Host, h } from '@stencil/core';
- @Component(${decorator.join('\n')})
- export class ${toPascalCase(tagName)} {
- render() {
- return (
- <Host>
- <slot></slot>
- </Host>
- );
- }
- }
- `;
- };
- /**
- * Get the boilerplate for style for a generated component
- * @returns a boilerplate CSS block
- */
- const getStyleUrlBoilerplate = () => `:host {
- display: block;
- }
- `;
- /**
- * Get the boilerplate for a file containing a spec (unit) test for a component
- * @param tagName the name of the tag associated with the component under test
- * @returns the contents of a file that unit tests a component
- */
- const getSpecTestBoilerplate = (tagName) => `import { newSpecPage } from '@stencil/core/testing';
- import { ${toPascalCase(tagName)} } from '../${tagName}';
- describe('${tagName}', () => {
- it('renders', async () => {
- const page = await newSpecPage({
- components: [${toPascalCase(tagName)}],
- html: \`<${tagName}></${tagName}>\`,
- });
- expect(page.root).toEqualHtml(\`
- <${tagName}>
- <mock:shadow-root>
- <slot></slot>
- </mock:shadow-root>
- </${tagName}>
- \`);
- });
- });
- `;
- /**
- * Get the boilerplate for a file containing an end-to-end (E2E) test for a component
- * @param tagName the name of the tag associated with the component under test
- * @returns the contents of a file that E2E tests a component
- */
- const getE2eTestBoilerplate = (tagName) => `import { newE2EPage } from '@stencil/core/testing';
- describe('${tagName}', () => {
- it('renders', async () => {
- const page = await newE2EPage();
- await page.setContent('<${tagName}></${tagName}>');
- const element = await page.find('${tagName}');
- expect(element).toHaveClass('hydrated');
- });
- });
- `;
- /**
- * Convert a dash case string to pascal case.
- * @param str the string to convert
- * @returns the converted input as pascal case
- */
- const toPascalCase = (str) => str.split('-').reduce((res, part) => res + part[0].toUpperCase() + part.slice(1), '');
- /**
- * Entrypoint for the Telemetry task
- * @param flags configuration flags provided to Stencil when a task was called (either this task or a task that invokes
- * telemetry)
- * @param sys the abstraction for interfacing with the operating system
- * @param logger a logging implementation to log the results out to the user
- */
- const taskTelemetry = async (flags, sys, logger) => {
- const prompt = logger.dim(sys.details.platform === 'windows' ? '>' : '$');
- const isEnabling = flags.args.includes('on');
- const isDisabling = flags.args.includes('off');
- const INFORMATION = `Opt in or out of telemetry. Information about the data we collect is available on our website: ${logger.bold('https://stenciljs.com/telemetry')}`;
- const THANK_YOU = `Thank you for helping to make Stencil better! 💖`;
- const ENABLED_MESSAGE = `${logger.green('Enabled')}. ${THANK_YOU}\n\n`;
- const DISABLED_MESSAGE = `${logger.red('Disabled')}\n\n`;
- const hasTelemetry = await checkTelemetry(sys);
- if (isEnabling) {
- const result = await enableTelemetry(sys);
- result
- ? console.log(`\n ${logger.bold('Telemetry is now ') + ENABLED_MESSAGE}`)
- : console.log(`Something went wrong when enabling Telemetry.`);
- return;
- }
- if (isDisabling) {
- const result = await disableTelemetry(sys);
- result
- ? console.log(`\n ${logger.bold('Telemetry is now ') + DISABLED_MESSAGE}`)
- : console.log(`Something went wrong when disabling Telemetry.`);
- return;
- }
- console.log(` ${logger.bold('Telemetry:')} ${logger.dim(INFORMATION)}`);
- console.log(`\n ${logger.bold('Status')}: ${hasTelemetry ? ENABLED_MESSAGE : DISABLED_MESSAGE}`);
- console.log(` ${prompt} ${logger.green('stencil telemetry [off|on]')}
- ${logger.cyan('off')} ${logger.dim('.............')} Disable sharing anonymous usage data
- ${logger.cyan('on')} ${logger.dim('..............')} Enable sharing anonymous usage data
- `);
- };
- /**
- * Entrypoint for the Help task, providing Stencil usage context to the user
- * @param flags configuration flags provided to Stencil when a task was call (either this task or a task that invokes
- * telemetry)
- * @param logger a logging implementation to log the results out to the user
- * @param sys the abstraction for interfacing with the operating system
- */
- const taskHelp = async (flags, logger, sys) => {
- const prompt = logger.dim(sys.details.platform === 'windows' ? '>' : '$');
- console.log(`
- ${logger.bold('Build:')} ${logger.dim('Build components for development or production.')}
- ${prompt} ${logger.green('stencil build [--dev] [--watch] [--prerender] [--debug]')}
- ${logger.cyan('--dev')} ${logger.dim('.............')} Development build
- ${logger.cyan('--watch')} ${logger.dim('...........')} Rebuild when files update
- ${logger.cyan('--serve')} ${logger.dim('...........')} Start the dev-server
- ${logger.cyan('--prerender')} ${logger.dim('.......')} Prerender the application
- ${logger.cyan('--docs')} ${logger.dim('............')} Generate component readme.md docs
- ${logger.cyan('--config')} ${logger.dim('..........')} Set stencil config file
- ${logger.cyan('--stats')} ${logger.dim('...........')} Write stencil-stats.json file
- ${logger.cyan('--log')} ${logger.dim('.............')} Write stencil-build.log file
- ${logger.cyan('--debug')} ${logger.dim('...........')} Set the log level to debug
- ${logger.bold('Test:')} ${logger.dim('Run unit and end-to-end tests.')}
- ${prompt} ${logger.green('stencil test [--spec] [--e2e]')}
- ${logger.cyan('--spec')} ${logger.dim('............')} Run unit tests with Jest
- ${logger.cyan('--e2e')} ${logger.dim('.............')} Run e2e tests with Puppeteer
- ${logger.bold('Generate:')} ${logger.dim('Bootstrap components.')}
- ${prompt} ${logger.green('stencil generate')} or ${logger.green('stencil g')}
- `);
- await taskTelemetry(flags, sys, logger);
- console.log(`
- ${logger.bold('Examples:')}
- ${prompt} ${logger.green('stencil build --dev --watch --serve')}
- ${prompt} ${logger.green('stencil build --prerender')}
- ${prompt} ${logger.green('stencil test --spec --e2e')}
- ${prompt} ${logger.green('stencil telemetry on')}
- ${prompt} ${logger.green('stencil generate')}
- ${prompt} ${logger.green('stencil g my-component')}
- `);
- };
- const taskInfo = (coreCompiler, sys, logger) => {
- const details = sys.details;
- const versions = coreCompiler.versions;
- console.log(``);
- console.log(`${logger.cyan(' System:')} ${sys.name} ${sys.version}`);
- console.log(`${logger.cyan(' Plaform:')} ${details.platform} (${details.release})`);
- console.log(`${logger.cyan(' CPU Model:')} ${details.cpuModel} (${sys.hardwareConcurrency} cpu${sys.hardwareConcurrency !== 1 ? 's' : ''})`);
- console.log(`${logger.cyan(' Compiler:')} ${sys.getCompilerExecutingPath()}`);
- console.log(`${logger.cyan(' Build:')} ${coreCompiler.buildId}`);
- console.log(`${logger.cyan(' Stencil:')} ${coreCompiler.version}${logger.emoji(' ' + coreCompiler.vermoji)}`);
- console.log(`${logger.cyan(' TypeScript:')} ${versions.typescript}`);
- console.log(`${logger.cyan(' Rollup:')} ${versions.rollup}`);
- console.log(`${logger.cyan(' Parse5:')} ${versions.parse5}`);
- console.log(`${logger.cyan(' Sizzle:')} ${versions.sizzle}`);
- console.log(`${logger.cyan(' Terser:')} ${versions.terser}`);
- console.log(``);
- };
- const taskServe = async (config) => {
- config.suppressLogs = true;
- config.flags.serve = true;
- config.devServer.openBrowser = config.flags.open;
- config.devServer.reloadStrategy = null;
- config.devServer.initialLoadUrl = '/';
- config.devServer.websocket = false;
- config.maxConcurrentWorkers = 1;
- config.devServer.root = isString(config.flags.root) ? config.flags.root : config.sys.getCurrentDirectory();
- const devServerPath = config.sys.getDevServerExecutingPath();
- const { start } = await config.sys.dynamicImport(devServerPath);
- const devServer = await start(config.devServer, config.logger);
- console.log(`${config.logger.cyan(' Root:')} ${devServer.root}`);
- console.log(`${config.logger.cyan(' Address:')} ${devServer.address}`);
- console.log(`${config.logger.cyan(' Port:')} ${devServer.port}`);
- console.log(`${config.logger.cyan(' Server:')} ${devServer.browserUrl}`);
- console.log(``);
- config.sys.onProcessInterrupt(() => {
- if (devServer) {
- config.logger.debug(`dev server close: ${devServer.browserUrl}`);
- devServer.close();
- }
- });
- };
- /**
- * Entrypoint for any Stencil tests
- * @param config a validated Stencil configuration entity
- */
- const taskTest = async (config) => {
- if (!IS_NODE_ENV) {
- config.logger.error(`"test" command is currently only implemented for a NodeJS environment`);
- return config.sys.exit(1);
- }
- config.buildDocs = false;
- const testingRunOpts = {
- e2e: !!config.flags.e2e,
- screenshot: !!config.flags.screenshot,
- spec: !!config.flags.spec,
- updateScreenshot: !!config.flags.updateScreenshot,
- };
- // always ensure we have jest modules installed
- const ensureModuleIds = ['@types/jest', 'jest', 'jest-cli'];
- if (testingRunOpts.e2e) {
- // if it's an e2e test, also make sure we're got
- // puppeteer modules installed and if browserExecutablePath is provided don't download Chromium use only puppeteer-core instead
- const puppeteer = config.testing.browserExecutablePath ? 'puppeteer-core' : 'puppeteer';
- ensureModuleIds.push(puppeteer);
- if (testingRunOpts.screenshot) {
- // ensure we've got pixelmatch for screenshots
- config.logger.warn(config.logger.yellow(`EXPERIMENTAL: screenshot visual diff testing is currently under heavy development and has not reached a stable status. However, any assistance testing would be appreciated.`));
- }
- }
- // ensure we've got the required modules installed
- const diagnostics = await config.sys.lazyRequire.ensure(config.rootDir, ensureModuleIds);
- if (diagnostics.length > 0) {
- config.logger.printDiagnostics(diagnostics);
- return config.sys.exit(1);
- }
- try {
- // let's test!
- const { createTesting } = await Promise.resolve().then(function () { return /*#__PURE__*/_interopNamespace(require('../testing/index.js')); });
- const testing = await createTesting(config);
- const passed = await testing.run(testingRunOpts);
- await testing.destroy();
- if (!passed) {
- return config.sys.exit(1);
- }
- }
- catch (e) {
- config.logger.error(e);
- return config.sys.exit(1);
- }
- };
- const run = async (init) => {
- const { args, logger, sys } = init;
- try {
- const flags = parseFlags(args);
- const task = flags.task;
- if (flags.debug || flags.verbose) {
- logger.setLevel('debug');
- }
- if (flags.ci) {
- logger.enableColors(false);
- }
- if (isFunction(sys.applyGlobalPatch)) {
- sys.applyGlobalPatch(sys.getCurrentDirectory());
- }
- if (!task || task === 'help' || flags.help) {
- await taskHelp(createConfigFlags({ task: 'help', args }), logger, sys);
- return;
- }
- startupLog(logger, task);
- const findConfigResults = await findConfig({ sys, configPath: flags.config });
- if (hasError(findConfigResults.diagnostics)) {
- logger.printDiagnostics(findConfigResults.diagnostics);
- return sys.exit(1);
- }
- const ensureDepsResults = await sys.ensureDependencies({
- rootDir: findConfigResults.rootDir,
- logger,
- dependencies: dependencies,
- });
- if (hasError(ensureDepsResults.diagnostics)) {
- logger.printDiagnostics(ensureDepsResults.diagnostics);
- return sys.exit(1);
- }
- const coreCompiler = await loadCoreCompiler(sys);
- if (task === 'version' || flags.version) {
- console.log(coreCompiler.version);
- return;
- }
- startupLogVersion(logger, task, coreCompiler);
- loadedCompilerLog(sys, logger, flags, coreCompiler);
- if (task === 'info') {
- taskInfo(coreCompiler, sys, logger);
- return;
- }
- const validated = await coreCompiler.loadConfig({
- config: {
- flags,
- },
- configPath: findConfigResults.configPath,
- logger,
- sys,
- });
- if (validated.diagnostics.length > 0) {
- logger.printDiagnostics(validated.diagnostics);
- if (hasError(validated.diagnostics)) {
- return sys.exit(1);
- }
- }
- if (isFunction(sys.applyGlobalPatch)) {
- sys.applyGlobalPatch(validated.config.rootDir);
- }
- await sys.ensureResources({ rootDir: validated.config.rootDir, logger, dependencies: dependencies });
- await telemetryAction(sys, validated.config, coreCompiler, async () => {
- await runTask(coreCompiler, validated.config, task, sys);
- });
- }
- catch (e) {
- if (!shouldIgnoreError(e)) {
- const details = `${logger.getLevel() === 'debug' && e instanceof Error ? e.stack : ''}`;
- logger.error(`uncaught cli error: ${e}${details}`);
- return sys.exit(1);
- }
- }
- };
- /**
- * Run a specified task
- * @param coreCompiler an instance of a minimal, bootstrap compiler for running the specified task
- * @param config a configuration for the Stencil project to apply to the task run
- * @param task the task to run
- * @param sys the {@link CompilerSystem} for interacting with the operating system
- * @public
- */
- const runTask = async (coreCompiler, config, task, sys) => {
- var _a, _b, _c, _d, _e, _f;
- const logger = (_a = config.logger) !== null && _a !== void 0 ? _a : createLogger();
- const strictConfig = {
- ...config,
- flags: createConfigFlags((_b = config.flags) !== null && _b !== void 0 ? _b : { task }),
- logger,
- outputTargets: (_c = config.outputTargets) !== null && _c !== void 0 ? _c : [],
- rootDir: (_d = config.rootDir) !== null && _d !== void 0 ? _d : '/',
- sys: (_e = sys !== null && sys !== void 0 ? sys : config.sys) !== null && _e !== void 0 ? _e : coreCompiler.createSystem({ logger }),
- testing: (_f = config.testing) !== null && _f !== void 0 ? _f : {},
- };
- switch (task) {
- case 'build':
- await taskBuild(coreCompiler, strictConfig);
- break;
- case 'docs':
- await taskDocs(coreCompiler, strictConfig);
- break;
- case 'generate':
- case 'g':
- await taskGenerate(coreCompiler, strictConfig);
- break;
- case 'help':
- await taskHelp(strictConfig.flags, strictConfig.logger, sys);
- break;
- case 'prerender':
- await taskPrerender(coreCompiler, strictConfig);
- break;
- case 'serve':
- await taskServe(strictConfig);
- break;
- case 'telemetry':
- await taskTelemetry(strictConfig.flags, sys, strictConfig.logger);
- break;
- case 'test':
- await taskTest(strictConfig);
- break;
- case 'version':
- console.log(coreCompiler.version);
- break;
- default:
- strictConfig.logger.error(`${strictConfig.logger.emoji('❌ ')}Invalid stencil command, please see the options below:`);
- await taskHelp(strictConfig.flags, strictConfig.logger, sys);
- return config.sys.exit(1);
- }
- };
- exports.parseFlags = parseFlags;
- exports.run = run;
- exports.runTask = runTask;
- //# sourceMappingURL=index.cjs.map
|