-
Notifications
You must be signed in to change notification settings - Fork 795
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(cli): [STENCIL-33] Writing and Reading the Ionic config file (#2963
) - Adds ability to CLI to allow writing and reading to the ~/.ionic/config.json file. When reading, if the file doesn't exist, readConfig will automatically create the file. Satisfies ADR-2
- Loading branch information
1 parent
8c375bd
commit f981812
Showing
13 changed files
with
437 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
import { getCompilerSystem } from './state/stencil-cli-config'; | ||
import { readJson, uuidv4 } from './telemetry/helpers'; | ||
|
||
export const defaultConfig = () => | ||
getCompilerSystem().resolvePath(`${getCompilerSystem().homeDir()}/.ionic/config.json`); | ||
|
||
export const defaultConfigDirectory = () => getCompilerSystem().resolvePath(`${getCompilerSystem().homeDir()}/.ionic`); | ||
|
||
export interface TelemetryConfig { | ||
'telemetry.stencil'?: boolean; | ||
'tokens.telemetry'?: string; | ||
} | ||
|
||
export async function readConfig(): Promise<TelemetryConfig> { | ||
let config: TelemetryConfig = await readJson(defaultConfig()); | ||
|
||
if (!config) { | ||
config = { | ||
'tokens.telemetry': uuidv4(), | ||
'telemetry.stencil': true, | ||
}; | ||
|
||
await writeConfig(config); | ||
} | ||
|
||
return config; | ||
} | ||
|
||
export async function writeConfig(config: TelemetryConfig): Promise<void> { | ||
try { | ||
await getCompilerSystem().createDir(defaultConfigDirectory(), { recursive: true }); | ||
await getCompilerSystem().writeFile(defaultConfig(), JSON.stringify(config)); | ||
} catch (error) { | ||
console.error(`Stencil Telemetry: couldn't write configuration file to ${defaultConfig()} - ${error}.`); | ||
} | ||
} | ||
|
||
export async function updateConfig(newOptions: TelemetryConfig): Promise<void> { | ||
const config = await readConfig(); | ||
await writeConfig(Object.assign(config, newOptions)); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,98 @@ | ||
import type { Logger, CompilerSystem, ConfigFlags } from '../../declarations'; | ||
export type CoreCompiler = typeof import('@stencil/core/compiler'); | ||
|
||
export interface StencilCLIConfigArgs { | ||
task?: string; | ||
args: string[]; | ||
logger: Logger; | ||
sys: CompilerSystem; | ||
flags?: ConfigFlags; | ||
coreCompiler?: CoreCompiler; | ||
} | ||
|
||
export default class StencilCLIConfig { | ||
static instance: StencilCLIConfig; | ||
|
||
private _args: string[]; | ||
private _logger: Logger; | ||
private _sys: CompilerSystem; | ||
private _flags: ConfigFlags | undefined; | ||
private _task: string | undefined; | ||
private _coreCompiler: CoreCompiler | undefined; | ||
|
||
private constructor(options: StencilCLIConfigArgs) { | ||
this._args = options?.args || []; | ||
this._logger = options?.logger; | ||
this._sys = options?.sys; | ||
} | ||
|
||
public static getInstance(options?: StencilCLIConfigArgs): StencilCLIConfig { | ||
if (!StencilCLIConfig.instance) { | ||
StencilCLIConfig.instance = new StencilCLIConfig(options); | ||
} | ||
|
||
return StencilCLIConfig.instance; | ||
} | ||
|
||
public get logger() { | ||
return this._logger; | ||
} | ||
public set logger(logger: Logger) { | ||
this._logger = logger; | ||
} | ||
|
||
public get sys() { | ||
return this._sys; | ||
} | ||
public set sys(sys: CompilerSystem) { | ||
this._sys = sys; | ||
} | ||
|
||
public get args() { | ||
return this._args; | ||
} | ||
public set args(args: string[]) { | ||
this._args = args; | ||
} | ||
|
||
public get task() { | ||
return this._task; | ||
} | ||
public set task(task: string) { | ||
this._task = task; | ||
} | ||
|
||
public get flags() { | ||
return this._flags; | ||
} | ||
public set flags(flags: ConfigFlags) { | ||
this._flags = flags; | ||
} | ||
|
||
public get coreCompiler() { | ||
return this._coreCompiler; | ||
} | ||
public set coreCompiler(coreCompiler: CoreCompiler) { | ||
this._coreCompiler = coreCompiler; | ||
} | ||
} | ||
|
||
export function initializeStencilCLIConfig(options: StencilCLIConfigArgs): StencilCLIConfig { | ||
return StencilCLIConfig.getInstance(options); | ||
} | ||
|
||
export function getStencilCLIConfig(): StencilCLIConfig { | ||
return StencilCLIConfig.getInstance(); | ||
} | ||
|
||
export function getCompilerSystem(): CompilerSystem { | ||
return getStencilCLIConfig().sys; | ||
} | ||
|
||
export function getLogger(): Logger { | ||
return getStencilCLIConfig().logger; | ||
} | ||
|
||
export function getCoreCompiler(): CoreCompiler { | ||
return getStencilCLIConfig().coreCompiler; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
import { createLogger } from '../../../compiler/sys/logger/console-logger'; | ||
import { createSystem } from '../../../compiler/sys/stencil-sys'; | ||
import { | ||
getCompilerSystem, | ||
getStencilCLIConfig, | ||
initializeStencilCLIConfig, | ||
getLogger, | ||
getCoreCompiler, | ||
} from '../stencil-cli-config'; | ||
|
||
describe('StencilCLIConfig', () => { | ||
const config = initializeStencilCLIConfig({ | ||
sys: createSystem(), | ||
args: [], | ||
logger: createLogger(), | ||
}); | ||
|
||
it('should behave as a singleton', () => { | ||
const config2 = initializeStencilCLIConfig({ | ||
sys: createSystem(), | ||
args: [], | ||
logger: createLogger(), | ||
}); | ||
|
||
expect(config2).toBe(config); | ||
|
||
const config3 = getStencilCLIConfig(); | ||
|
||
expect(config3).toBe(config); | ||
}); | ||
|
||
it('allows updating any item', () => { | ||
config.args = ['nice', 'awesome']; | ||
expect(config.args).toEqual(['nice', 'awesome']); | ||
}); | ||
|
||
it('getCompilerSystem should return a segment of the singleton', () => { | ||
expect(config.sys).toBe(getCompilerSystem()); | ||
}); | ||
|
||
it('getLogger should return a segment of the singleton', () => { | ||
expect(config.logger).toBe(getLogger()); | ||
}); | ||
|
||
it('getCoreCompiler should return a segment of the singleton', () => { | ||
expect(config.coreCompiler).toBe(getCoreCompiler()); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,53 @@ | ||
import { getCompilerSystem, getStencilCLIConfig } from '../state/stencil-cli-config'; | ||
|
||
interface TerminalInfo { | ||
/** | ||
* Whether this is in CI or not. | ||
*/ | ||
readonly ci: boolean; | ||
/** | ||
* Whether the terminal is an interactive TTY or not. | ||
*/ | ||
readonly tty: boolean; | ||
} | ||
|
||
export declare const TERMINAL_INFO: TerminalInfo; | ||
|
||
export const tryFn = async <T extends (...args: any[]) => Promise<R>, R>(fn: T, ...args: any[]): Promise<R | null> => { | ||
try { | ||
return await fn(...args); | ||
} catch { | ||
// ignore | ||
} | ||
|
||
return null; | ||
}; | ||
|
||
export const isInteractive = (object?: TerminalInfo): boolean => { | ||
const terminalInfo = | ||
object || | ||
Object.freeze({ | ||
tty: getCompilerSystem().isTTY() ? true : false, | ||
ci: | ||
['CI', 'BUILD_ID', 'BUILD_NUMBER', 'BITBUCKET_COMMIT', 'CODEBUILD_BUILD_ARN'].filter( | ||
v => !!getCompilerSystem().getEnvironmentVar(v), | ||
).length > 0 || !!getStencilCLIConfig()?.flags?.ci, | ||
}); | ||
|
||
return terminalInfo.tty && !terminalInfo.ci; | ||
}; | ||
|
||
// Plucked from https://github.com/ionic-team/capacitor/blob/b893a57aaaf3a16e13db9c33037a12f1a5ac92e0/cli/src/util/uuid.ts | ||
export function uuidv4(): string { | ||
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); | ||
}); | ||
} | ||
|
||
export async function readJson(path: string) { | ||
const file = await getCompilerSystem().readFile(path); | ||
return !!file && JSON.parse(file); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,66 @@ | ||
import { initializeStencilCLIConfig } from '../../state/stencil-cli-config'; | ||
import { isInteractive, TERMINAL_INFO, tryFn, uuidv4 } from '../helpers'; | ||
import { createSystem } from '../../../compiler/sys/stencil-sys'; | ||
import { mockLogger } from '@stencil/core/testing'; | ||
|
||
describe('uuidv4', () => { | ||
it('outputs a UUID', () => { | ||
const pattern = 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); | ||
const uuid = uuidv4(); | ||
expect(!!uuid.match(pattern)).toBe(true); | ||
}); | ||
}); | ||
|
||
describe('isInteractive', () => { | ||
initializeStencilCLIConfig({ | ||
sys: createSystem(), | ||
logger: mockLogger(), | ||
args: [], | ||
}); | ||
|
||
it('returns false by default', () => { | ||
const result = isInteractive(); | ||
expect(result).toBe(false); | ||
}); | ||
|
||
it('returns false when tty is false', () => { | ||
const result = isInteractive({ ci: true, tty: false }); | ||
expect(result).toBe(false); | ||
}); | ||
|
||
it('returns false when ci is true', () => { | ||
const result = isInteractive({ ci: true, tty: true }); | ||
expect(result).toBe(false); | ||
}); | ||
|
||
it('returns true when tty is true and ci is false', () => { | ||
const result = isInteractive({ ci: false, tty: true }); | ||
expect(result).toBe(true); | ||
}); | ||
}); | ||
|
||
describe('tryFn', () => { | ||
it('handles failures correctly', async () => { | ||
const result = await tryFn(async () => { | ||
throw new Error('Uh oh!'); | ||
}); | ||
|
||
expect(result).toBe(null); | ||
}); | ||
|
||
it('handles success correctly', async () => { | ||
const result = await tryFn(async () => { | ||
return true; | ||
}); | ||
|
||
expect(result).toBe(true); | ||
}); | ||
|
||
it('handles returning false correctly', async () => { | ||
const result = await tryFn(async () => { | ||
return false; | ||
}); | ||
|
||
expect(result).toBe(false); | ||
}); | ||
}); |
Oops, something went wrong.