@lexjs/web 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (63) hide show
  1. package/LICENSE +16 -0
  2. package/README.md +800 -0
  3. package/dist/actions/core-actions.js +6 -0
  4. package/dist/actions/npm-actions.js +38 -0
  5. package/dist/actions/permission-actions.js +21 -0
  6. package/dist/api/browser/browser.js +111 -0
  7. package/dist/api/browser/browser.types.js +1 -0
  8. package/dist/api/matchers/matchers.js +9 -0
  9. package/dist/api/site/site.js +248 -0
  10. package/dist/api/site/site.types.js +1 -0
  11. package/dist/api/utils/extract-protocol.js +10 -0
  12. package/dist/api/utils/remove-protocol.js +8 -0
  13. package/dist/api/utils/return-type-guard.js +7 -0
  14. package/dist/api/utils/slash.js +21 -0
  15. package/dist/command/args/config-args.js +9 -0
  16. package/dist/command/args/data-args.js +110 -0
  17. package/dist/command/args/invalid-args.js +11 -0
  18. package/dist/command/args/query-args.js +121 -0
  19. package/dist/command/args/validate-args.js +126 -0
  20. package/dist/command/options.js +58 -0
  21. package/dist/config/config-file.js +66 -0
  22. package/dist/config/config-flags.js +42 -0
  23. package/dist/config/file-editor.js +26 -0
  24. package/dist/config/handle-config.js +12 -0
  25. package/dist/constants.js +15 -0
  26. package/dist/create-app/main.js +121 -0
  27. package/dist/create-app/paths.js +24 -0
  28. package/dist/create-app/schema/base.json +21 -0
  29. package/dist/create-app/schema/browsers.json +60 -0
  30. package/dist/create-app/schema/sites.json +71 -0
  31. package/dist/create-app/script-contents.js +18 -0
  32. package/dist/create-app/tree.js +25 -0
  33. package/dist/data/app-data.js +12 -0
  34. package/dist/data/config-data.js +35 -0
  35. package/dist/data/defaults.js +22 -0
  36. package/dist/data/initial-sites.js +36 -0
  37. package/dist/data/sort-config-data.js +17 -0
  38. package/dist/helpers/find-browser.js +9 -0
  39. package/dist/helpers/find-config-item.js +23 -0
  40. package/dist/helpers/find-nested.js +24 -0
  41. package/dist/helpers/find-profile.js +11 -0
  42. package/dist/helpers/find-site.js +15 -0
  43. package/dist/helpers/get-browser-name.js +9 -0
  44. package/dist/helpers/get-query-profiles.js +34 -0
  45. package/dist/main.js +19 -0
  46. package/dist/query/get-profiles-config.js +10 -0
  47. package/dist/query/get-query-browsers.js +33 -0
  48. package/dist/query/get-query-sites.js +49 -0
  49. package/dist/query/get-urls.js +85 -0
  50. package/dist/query/open-urls.js +30 -0
  51. package/dist/query/print-query.js +24 -0
  52. package/dist/query/query.js +19 -0
  53. package/dist/types/arg.types.js +1 -0
  54. package/dist/types/config.types.js +1 -0
  55. package/dist/types/primitive.type.js +1 -0
  56. package/dist/types/query.types.js +1 -0
  57. package/dist/types/utility.types.js +1 -0
  58. package/dist/utils/get-package-info.js +10 -0
  59. package/dist/utils/logger.js +15 -0
  60. package/dist/utils/parse-data.js +15 -0
  61. package/dist/utils/transform-text.js +7 -0
  62. package/dist/utils/yaml-files.js +6 -0
  63. package/package.json +88 -0
@@ -0,0 +1,121 @@
1
+ import yargs from 'yargs';
2
+ import { hideBin } from 'yargs/helpers';
3
+ import { matchers } from '../../api/matchers/matchers.js';
4
+ import { configFlags } from '../../config/config-flags.js';
5
+ import { getPackageInfo } from '../../utils/get-package-info.js';
6
+ import { QueryOptions as Options, queryAlias as alias, queryOptions as options, queryOptionTypes as types, } from '../options.js';
7
+ const version = getPackageInfo().version;
8
+ const group = {
9
+ browser: 'Browser Options:',
10
+ site: 'Site Options:',
11
+ other: 'Other Options:',
12
+ };
13
+ const parsed = yargs(hideBin(process.argv))
14
+ .option(Options.Browser, {
15
+ type: types[Options.Browser],
16
+ alias: alias.browser,
17
+ description: 'The browser app to open',
18
+ group: group.browser,
19
+ })
20
+ .option(Options.Profile, {
21
+ type: types[Options.Profile],
22
+ alias: alias.profile,
23
+ description: 'The browser profile to open',
24
+ group: group.browser,
25
+ })
26
+ .option(Options.Incognito, {
27
+ type: types[Options.Incognito],
28
+ alias: alias.incognito,
29
+ description: 'Open in incognito / private mode',
30
+ group: group.browser,
31
+ })
32
+ .option(Options.Site, {
33
+ type: types[Options.Site],
34
+ alias: alias.site,
35
+ description: 'The website to search',
36
+ group: group.site,
37
+ })
38
+ .option(Options.Query, {
39
+ type: types[Options.Query],
40
+ alias: alias.query,
41
+ description: "The website's query path to use for searching",
42
+ group: group.site,
43
+ })
44
+ .option(Options.Resource, {
45
+ type: types[Options.Resource],
46
+ alias: alias.resource,
47
+ description: "The website's resource to access",
48
+ group: group.site,
49
+ })
50
+ .option(Options.Delimiter, {
51
+ type: types[Options.Delimiter],
52
+ alias: alias.delimiter,
53
+ description: "The website's delimiter to separate search terms",
54
+ group: group.site,
55
+ })
56
+ .option(Options.Port, {
57
+ type: types[Options.Port],
58
+ alias: alias.port,
59
+ description: 'The port number to add to the URL',
60
+ group: group.site,
61
+ })
62
+ .option(Options.Http, {
63
+ type: types[Options.Http],
64
+ alias: alias.http,
65
+ description: 'Use the HTTP (non-secure) protocol',
66
+ group: group.site,
67
+ })
68
+ .option(Options.Split, {
69
+ type: types[Options.Split],
70
+ alias: alias.split,
71
+ description: 'Create a separate search query for each search term',
72
+ group: group.other,
73
+ })
74
+ .option(Options.DryRun, {
75
+ type: types[Options.DryRun],
76
+ alias: alias['dry-run'],
77
+ description: 'Display the output without opening browser tabs',
78
+ group: group.other,
79
+ })
80
+ .middleware((argv) => {
81
+ for (const key of Object.keys(argv)) {
82
+ if (key === 'dry-run' && !('dryrun' in argv)) {
83
+ argv.dryrun = argv[key];
84
+ }
85
+ }
86
+ })
87
+ .help()
88
+ .version(version)
89
+ .hide('help')
90
+ .hide('version')
91
+ .boolean(configFlags.filter((flag) => !options.includes(flag)))
92
+ .parserConfiguration({
93
+ 'boolean-negation': false,
94
+ })
95
+ .parseSync();
96
+ const args = parsed;
97
+ const arrayArgs = {
98
+ browser: args.browser,
99
+ profile: args.profile,
100
+ site: args.site,
101
+ query: args.query,
102
+ resource: args.resource,
103
+ port: args.port,
104
+ };
105
+ const _ = args._.map((arg) => `${arg}`);
106
+ const urlsOnly = _.length > 0 && _.every((arg) => matchers.url.test(arg));
107
+ /**
108
+ * Is populated only if all value args are URLs
109
+ */
110
+ const urlArgs = urlsOnly ? _ : undefined;
111
+ const queryArgs = {
112
+ ...args,
113
+ ...arrayArgs,
114
+ _,
115
+ };
116
+ for (const key of Object.keys(queryArgs)) {
117
+ if (args[key] == null) {
118
+ delete queryArgs[key];
119
+ }
120
+ }
121
+ export { queryArgs, urlArgs };
@@ -0,0 +1,126 @@
1
+ import chalk from 'chalk';
2
+ import { matchers } from '../../api/matchers/matchers.js';
3
+ import { configSiteFlags, browserProfileFlags } from '../../config/config-flags.js';
4
+ import { defaultsData } from '../../data/defaults.js';
5
+ import { getBrowserName } from '../../helpers/get-browser-name.js';
6
+ import { logger } from '../../utils/logger.js';
7
+ import { QueryOptions as Options } from '../options.js';
8
+ import { dataArgs } from './data-args.js';
9
+ import { invalidArgs } from './invalid-args.js';
10
+ import { queryArgs, urlArgs } from './query-args.js';
11
+ const { italic } = chalk;
12
+ const { resource, query, delimiter } = queryArgs;
13
+ const siteArgs = dataArgs.site(false);
14
+ const browserArgs = dataArgs.browser(false);
15
+ const portArgs = dataArgs.port();
16
+ const validate = {
17
+ profiles: false,
18
+ };
19
+ const errorMessages = [];
20
+ function addMessage(message) {
21
+ errorMessages.push(message);
22
+ }
23
+ function isEmptyArg(list) {
24
+ return list.length === 1 && list[0] === '';
25
+ }
26
+ function noValueError(option) {
27
+ addMessage(logger.level.error(`${italic(`--${option}`)} option must have a value`));
28
+ }
29
+ function validateResource(value, option, allowUrlArgs = true) {
30
+ const emptyArg = !Array.isArray(value) && value === '';
31
+ const emptyList = Array.isArray(value) && value.every((arg) => arg === '');
32
+ if (emptyArg || emptyList) {
33
+ noValueError(option);
34
+ }
35
+ if (siteArgs.length === 0 && (!allowUrlArgs || !urlArgs)) {
36
+ addMessage(logger.level.error(`${italic(`--${option}`)} option must be used with --engine${allowUrlArgs ? ' or URL' : ''}`));
37
+ }
38
+ }
39
+ /**
40
+ * Validates profile args
41
+ *
42
+ * @param browser
43
+ * * If a single string is provided - profile args are checked against
44
+ * profile keys and aliases of the provided config browser
45
+ * * String array, null or undefined - profile args are validated against
46
+ * profile keys and aliases of all config browsers
47
+ */
48
+ function validateProfileArgs(browser) {
49
+ const profileArgs = dataArgs.profile(browser == null || Array.isArray(browser) ? null : browser, false);
50
+ if (isEmptyArg(profileArgs)) {
51
+ noValueError(Options.Profile);
52
+ }
53
+ let flags = {};
54
+ if (browser != null) {
55
+ (Array.isArray(browser) ? browser : [browser]).forEach((browserNameOrAlias) => {
56
+ const browserName = getBrowserName(browserNameOrAlias);
57
+ const profileFlags = browserProfileFlags[browserName];
58
+ flags = {
59
+ ...flags,
60
+ [browserName]: profileFlags ?? [],
61
+ };
62
+ });
63
+ }
64
+ const invalidProfiles = profileArgs.filter((arg) => arg !== '' && !Object.values(flags).flat().includes(arg));
65
+ if (invalidProfiles.length > 0) {
66
+ addMessage(logger.level.error(`Invalid profiles: ${logger.level.warning(invalidProfiles.join(' '))}`));
67
+ }
68
+ }
69
+ /**
70
+ * Returns an array of error messages with invalid args
71
+ * (empty array if all args are valid)
72
+ */
73
+ export function validateArgs() {
74
+ /* ~~~ VALIDATE CLI ARGS ~~~ */
75
+ if (invalidArgs.length > 0) {
76
+ addMessage(logger.level.error(`Invalid options: ${logger.level.warning(invalidArgs.join(', '))}`));
77
+ }
78
+ /* ~~~ VALIDATE SITE ARGS ~~~ */
79
+ if (isEmptyArg(siteArgs)) {
80
+ noValueError(Options.Site);
81
+ }
82
+ const invalidSites = siteArgs.filter((arg) => arg !== '' && !configSiteFlags.includes(arg) && !matchers.url.test(arg));
83
+ if (invalidSites.length > 0) {
84
+ addMessage(logger.level.error(`Invalid sites: ${logger.level.warning(invalidSites.join(' '))}`));
85
+ }
86
+ /* ~~~ VALIDATE RESOURCE ARGS ~~~ */
87
+ if (resource != null) {
88
+ validateResource(resource, Options.Resource);
89
+ }
90
+ /* ~~~ VALIDATE SEARCH ARGS ~~~ */
91
+ if (query != null) {
92
+ validateResource(query, Options.Query, false);
93
+ }
94
+ /* ~~~ VALIDATE DELIMITER ARGS ~~~ */
95
+ if (delimiter != null) {
96
+ if (Array.isArray(delimiter)) {
97
+ addMessage(logger.level.error(`Multiple ${italic('--delimiter')} options are not allowed`));
98
+ }
99
+ if (delimiter === '') {
100
+ noValueError(Options.Delimiter);
101
+ }
102
+ }
103
+ /**
104
+ * ~~~ VALIDATE BROWSER ARGS ~~~
105
+ *
106
+ * Only an empty browser option should is checked.
107
+ * A browser value that does not exist in the config should still be valid
108
+ * because it is an app that should be attempted to open
109
+ */
110
+ if (isEmptyArg(browserArgs)) {
111
+ noValueError(Options.Browser);
112
+ }
113
+ /* ~~~ VALIDATE PROFILE ARGS ~~~ */
114
+ if (validate.profiles) {
115
+ validateProfileArgs(browserArgs.length > 0 ? browserArgs : defaultsData.browser?.[0]);
116
+ }
117
+ /* ~~~ VALIDATE PORT ARGS ~~~ */
118
+ if (portArgs.length > 0) {
119
+ if (portArgs.some((port) => Number.isNaN(port))) {
120
+ addMessage(logger.level.error(`${italic('--port')} option must be a number`));
121
+ }
122
+ if (siteArgs.length === 0 && !urlArgs)
123
+ addMessage(logger.level.error(`${italic('--port')} option must be used with --engine or URL`));
124
+ }
125
+ return errorMessages.sort((a, b) => a.localeCompare(b));
126
+ }
@@ -0,0 +1,58 @@
1
+ export const yargsOptions = ['_', '$0'];
2
+ // QUERY OPTIONS
3
+ export var QueryOptions;
4
+ (function (QueryOptions) {
5
+ QueryOptions["Browser"] = "browser";
6
+ QueryOptions["Profile"] = "profile";
7
+ QueryOptions["Site"] = "site";
8
+ QueryOptions["Query"] = "query";
9
+ QueryOptions["Resource"] = "resource";
10
+ QueryOptions["Delimiter"] = "delimiter";
11
+ QueryOptions["Port"] = "port";
12
+ QueryOptions["Incognito"] = "incognito";
13
+ QueryOptions["Split"] = "split";
14
+ QueryOptions["Http"] = "http";
15
+ QueryOptions["DryRun"] = "dry-run";
16
+ })(QueryOptions || (QueryOptions = {}));
17
+ export const queryOptionTypes = {
18
+ [QueryOptions.Browser]: 'string',
19
+ [QueryOptions.Profile]: 'string',
20
+ [QueryOptions.Site]: 'string',
21
+ [QueryOptions.Query]: 'string',
22
+ [QueryOptions.Resource]: 'string',
23
+ [QueryOptions.Delimiter]: 'string',
24
+ [QueryOptions.Port]: 'number',
25
+ [QueryOptions.Incognito]: 'boolean',
26
+ [QueryOptions.Split]: 'boolean',
27
+ [QueryOptions.Http]: 'boolean',
28
+ [QueryOptions.DryRun]: 'boolean',
29
+ };
30
+ export const queryAlias = {
31
+ browser: ['b'],
32
+ profile: ['p'],
33
+ site: ['s'],
34
+ query: ['q'],
35
+ resource: ['r'],
36
+ delimiter: ['d'],
37
+ port: [':'],
38
+ incognito: ['i'],
39
+ 'dry-run': ['n'],
40
+ };
41
+ /**
42
+ * CLI options and their aliases (excluding config options)
43
+ */
44
+ export const queryOptions = [
45
+ ...Object.values(QueryOptions),
46
+ ...Object.values(queryAlias).flat(),
47
+ 'dryRun',
48
+ 'dryrun',
49
+ ];
50
+ // CONFIG OPTIONS
51
+ export var ConfigOptions;
52
+ (function (ConfigOptions) {
53
+ ConfigOptions["Config"] = "config";
54
+ })(ConfigOptions || (ConfigOptions = {}));
55
+ /**
56
+ * CLI options and their aliases related to config
57
+ */
58
+ export const configOptions = Object.values(ConfigOptions);
@@ -0,0 +1,66 @@
1
+ import path from 'node:path';
2
+ import $_ from '@lexjs/prompts';
3
+ import { openApp } from 'open';
4
+ import { useCoreActions } from '../actions/core-actions.js';
5
+ import { appData, writeAppData } from '../data/app-data.js';
6
+ import { logger } from '../utils/logger.js';
7
+ import { yamlFiles } from '../utils/yaml-files.js';
8
+ import { CONFIG_FILE } from '../constants.js';
9
+ import { updateFileEditor } from './file-editor.js';
10
+ const configChoices = [
11
+ { title: 'Sites', value: 'sites' },
12
+ { title: 'Browsers', value: 'browsers' },
13
+ ];
14
+ function normalizeChoiceArg(arg) {
15
+ const configOptions = ['browsers', 'sites'];
16
+ return configOptions.find((option) => arg != null && option.startsWith(arg));
17
+ }
18
+ const configDir = useCoreActions(({ config }) => config);
19
+ export function configFileExists(option) {
20
+ const configName = CONFIG_FILE(option);
21
+ return !!yamlFiles(configName).find((file) => configDir.exists(file));
22
+ }
23
+ export function getConfigFileName(option) {
24
+ const configName = CONFIG_FILE(option);
25
+ return (yamlFiles(configName).find((file) => configDir.exists(file)) ?? configName);
26
+ }
27
+ export function getConfigFilePath(option) {
28
+ const configName = getConfigFileName(option);
29
+ return path.join(configDir.getPath(), configName);
30
+ }
31
+ export function createInitialConfig(option) {
32
+ const configName = getConfigFileName(option);
33
+ configDir.writeFile(configName, `$schema: ../schema/${option}.json
34
+
35
+ `);
36
+ }
37
+ export async function openConfigFile(arg) {
38
+ const editor = appData.editor ?? (await updateFileEditor());
39
+ if (editor == null)
40
+ return;
41
+ const choiceArg = normalizeChoiceArg(arg);
42
+ const isConfigChoice = choiceArg === 'sites' || choiceArg === 'browsers';
43
+ let configOption = isConfigChoice
44
+ ? choiceArg
45
+ : undefined;
46
+ if (choiceArg == null || !isConfigChoice) {
47
+ const { selectedConfig } = await $_.select({
48
+ name: 'selectedConfig',
49
+ message: 'Select config to edit',
50
+ choices: configChoices,
51
+ });
52
+ configOption = selectedConfig;
53
+ }
54
+ if (configOption == null)
55
+ return;
56
+ if (!configFileExists(configOption)) {
57
+ createInitialConfig(configOption);
58
+ }
59
+ const subprocess = await openApp(editor, {
60
+ arguments: [getConfigFilePath(configOption)],
61
+ });
62
+ subprocess.on('error', () => {
63
+ writeAppData({ editor: undefined });
64
+ logger.error('Error opening file editor:', editor);
65
+ });
66
+ }
@@ -0,0 +1,42 @@
1
+ import { browsersData, sitesData, getProfilesData } from '../data/config-data.js';
2
+ /**
3
+ * Returns a flat array of names and aliases from the config
4
+ * based on the provided config data
5
+ */
6
+ function getFlags(data, includeSingleLetterAlias = true) {
7
+ return Object.entries(data)
8
+ .map(([key, { alias }]) => {
9
+ if (alias != null) {
10
+ const aliases = Array.isArray(alias) ? alias : [alias];
11
+ const filter = (a) => includeSingleLetterAlias || a.length > 1;
12
+ return [key, ...aliases.filter(filter)];
13
+ }
14
+ return key;
15
+ })
16
+ .flat();
17
+ }
18
+ /** a list of all browser keys and aliases in the config */
19
+ export const configBrowserFlags = getFlags(browsersData);
20
+ /** profile keys and aliases per each browser */
21
+ export const browserProfileFlags = {};
22
+ /** a list of all profile keys WITHOUT aliases in the config */
23
+ export const configProfileFlags = Object.keys(browsersData)
24
+ .map((browserName) => {
25
+ const profilesData = getProfilesData(browserName);
26
+ browserProfileFlags[browserName] = getFlags(profilesData);
27
+ return getFlags(profilesData, false);
28
+ })
29
+ .flat();
30
+ /** a list of all site keys and aliases in the config */
31
+ export const configSiteFlags = getFlags(sitesData);
32
+ /**
33
+ * A list of identifiers (keys and aliases) from config data:
34
+ * - browsers
35
+ * - profiles (no aliases)
36
+ * - sites
37
+ */
38
+ export const configFlags = [
39
+ ...configBrowserFlags,
40
+ ...configProfileFlags,
41
+ ...configSiteFlags,
42
+ ];
@@ -0,0 +1,26 @@
1
+ import $_ from '@lexjs/prompts';
2
+ import { writeAppData } from '../data/app-data.js';
3
+ import { VS_CODE } from '../constants.js';
4
+ export async function updateFileEditor() {
5
+ const { useVsCode } = await $_.confirm({
6
+ name: 'useVsCode',
7
+ message: 'Use VSCode to edit config?',
8
+ initial: true,
9
+ });
10
+ if (useVsCode == null)
11
+ return undefined;
12
+ if (useVsCode) {
13
+ writeAppData({ editor: VS_CODE });
14
+ return VS_CODE;
15
+ }
16
+ const { editor } = await $_.text({
17
+ name: 'editor',
18
+ message: 'Command to launch file editor',
19
+ validate: (value) => value !== '',
20
+ });
21
+ if (editor != null && editor !== '') {
22
+ writeAppData({ editor });
23
+ return editor;
24
+ }
25
+ return undefined;
26
+ }
@@ -0,0 +1,12 @@
1
+ import { configArgs } from '../command/args/config-args.js';
2
+ import { openConfigFile } from './config-file.js';
3
+ import { updateFileEditor } from './file-editor.js';
4
+ const _ = configArgs._.map((arg) => `${arg}`);
5
+ export async function handleConfig() {
6
+ const arg = _.at(0);
7
+ if (arg === 'editor') {
8
+ await updateFileEditor();
9
+ return;
10
+ }
11
+ await openConfigFile(arg);
12
+ }
@@ -0,0 +1,15 @@
1
+ import os from 'node:os';
2
+ import { getPackageInfo } from './utils/get-package-info.js';
3
+ const NODE_ENV = process.env.NODE_ENV;
4
+ export const PACKAGE_NAME = getPackageInfo().name;
5
+ export const PACKAGE_NAMESPACE = PACKAGE_NAME.split('/')[0].substring(1);
6
+ export const APP_NAME = PACKAGE_NAME.split('/')[1];
7
+ export const IS_WINDOWS = os.platform() === 'win32';
8
+ export const IS_DEV = NODE_ENV === 'development' || NODE_ENV === 'dev';
9
+ export const DATA_FILE = 'data.json';
10
+ export const CONFIG_FILE = (option) => `${option}.yml`;
11
+ export const BASE_SCHEMA_FILE = 'base.json';
12
+ export const SITES_SCHEMA_FILE = 'sites.json';
13
+ export const BROWSERS_SCHEMA_FILE = 'browsers.json';
14
+ export const COMMAND_FALLBACK = 'web';
15
+ export const VS_CODE = 'code';
@@ -0,0 +1,121 @@
1
+ #!/usr/bin/env node
2
+ import fs from 'node:fs';
3
+ import path from 'node:path';
4
+ import { createTree, FileTree } from '@lexjs/filetree';
5
+ import { coreActions } from '@lexjs/filetree/actions';
6
+ import $_ from '@lexjs/prompts';
7
+ import chalk from 'chalk';
8
+ import { useCoreActions } from '../actions/core-actions.js';
9
+ import { npmCommands, npmActions } from '../actions/npm-actions.js';
10
+ import { permissionActions } from '../actions/permission-actions.js';
11
+ import { appData } from '../data/app-data.js';
12
+ import { getPackageInfo } from '../utils/get-package-info.js';
13
+ import { logger } from '../utils/logger.js';
14
+ import { configFileExists, createInitialConfig, } from '../config/config-file.js';
15
+ import { DATA_FILE, IS_DEV, IS_WINDOWS, PACKAGE_NAME, COMMAND_FALLBACK, } from '../constants.js';
16
+ import { paths } from './paths.js';
17
+ import { bashScript, bashStartScript, powershellScript, } from './script-contents.js';
18
+ import { initialTree, tree } from './tree.js';
19
+ function isEmpty(str) {
20
+ return str == null || str === '';
21
+ }
22
+ async function getCommand() {
23
+ let command;
24
+ const rootDir = useCoreActions((root) => root);
25
+ if (rootDir.exists(DATA_FILE)) {
26
+ command = appData.command;
27
+ }
28
+ let renameCommand = false;
29
+ if (!isEmpty(command)) {
30
+ logger.warning(`${PACKAGE_NAME} command is set as ${chalk.underline(command)}\n`);
31
+ const { rename } = await $_.toggle({
32
+ message: 'Do you want to rename it?',
33
+ name: 'rename',
34
+ });
35
+ if (rename == null)
36
+ return undefined;
37
+ renameCommand = rename;
38
+ }
39
+ if (isEmpty(command) || renameCommand) {
40
+ const { commandName } = await $_.text({
41
+ name: 'commandName',
42
+ message: 'What should be the command name?',
43
+ initial: COMMAND_FALLBACK,
44
+ });
45
+ if (commandName == null)
46
+ return undefined;
47
+ command = commandName;
48
+ }
49
+ return command;
50
+ }
51
+ function ensureNamespace() {
52
+ const fileTree = new FileTree(paths.namespace, {});
53
+ if (!fs.existsSync(paths.namespace)) {
54
+ createTree(fileTree);
55
+ }
56
+ }
57
+ async function initializeApp(command) {
58
+ const fileTree = new FileTree(paths.root, initialTree);
59
+ ensureNamespace();
60
+ createTree(fileTree);
61
+ // create data and config files
62
+ const useCore = fileTree.use(coreActions);
63
+ const rootDir = useCore((root) => root);
64
+ rootDir.createFile(DATA_FILE, JSON.stringify({ ...appData, command }));
65
+ const configOptions = ['browsers', 'sites'];
66
+ configOptions.forEach((option) => {
67
+ if (!configFileExists(option)) {
68
+ createInitialConfig(option);
69
+ }
70
+ });
71
+ // install package (link in development)
72
+ const version = IS_DEV ? '' : getPackageInfo().version;
73
+ const pkg = version != null && version !== ''
74
+ ? `${PACKAGE_NAME}@${version}`
75
+ : PACKAGE_NAME;
76
+ const npmCommand = IS_DEV ? npmCommands.link : npmCommands.install;
77
+ const useNpm = fileTree.use(npmActions);
78
+ await useNpm(({ lib }) => lib)[npmCommand]([pkg]);
79
+ }
80
+ function linkDist() {
81
+ const distDir = useCoreActions(({ lib }) => lib.node_modules[PACKAGE_NAME].dist).getPath();
82
+ if (fs.existsSync(paths.distLink)) {
83
+ fs.rmSync(paths.distLink, { force: true, recursive: true });
84
+ }
85
+ fs.symlinkSync(distDir, paths.distLink, IS_WINDOWS ? 'junction' : 'dir');
86
+ }
87
+ async function createScriptFiles(command) {
88
+ const [rootDir, binDir] = useCoreActions((root) => [root, root.bin]);
89
+ const scriptNames = { bash: command, powershell: `${command}.ps1` };
90
+ const { bash, powershell } = scriptNames;
91
+ // delete files that are not named based on the command
92
+ const binFiles = fs.readdirSync(binDir.getPath());
93
+ binFiles
94
+ .filter((file) => {
95
+ const filePath = path.join(binDir.getPath(), file);
96
+ const isCommandFile = Object.values(scriptNames).includes(file);
97
+ return fs.statSync(filePath).isFile() && !isCommandFile;
98
+ })
99
+ .forEach((file) => {
100
+ binDir.deleteFile(file);
101
+ });
102
+ const fileTree = new FileTree(paths.root, tree);
103
+ const usePermissions = fileTree.use(permissionActions);
104
+ const binPermissions = usePermissions(({ bin }) => bin);
105
+ // create script files
106
+ rootDir.createFile('start.sh', bashStartScript);
107
+ binDir.createFile(bash, bashScript);
108
+ await binPermissions.x(bash);
109
+ if (IS_WINDOWS) {
110
+ binDir.createFile(powershell, powershellScript);
111
+ await binPermissions.x(bash);
112
+ }
113
+ }
114
+ (async function createApp() {
115
+ const command = await getCommand();
116
+ if (command == null)
117
+ return;
118
+ await initializeApp(command);
119
+ await createScriptFiles(command);
120
+ linkDist();
121
+ })();
@@ -0,0 +1,24 @@
1
+ import os from 'node:os';
2
+ import path from 'node:path';
3
+ import { APP_NAME, PACKAGE_NAMESPACE } from '../constants.js';
4
+ class Paths {
5
+ get namespace() {
6
+ return path.join(os.homedir(), `.${PACKAGE_NAMESPACE}`);
7
+ }
8
+ get root() {
9
+ return path.join(this.namespace, APP_NAME);
10
+ }
11
+ get bin() {
12
+ return path.join(this.root, 'bin');
13
+ }
14
+ get lib() {
15
+ return path.join(this.root, 'lib');
16
+ }
17
+ get distLink() {
18
+ return path.join(this.root, '.dist');
19
+ }
20
+ get main() {
21
+ return path.join(this.distLink, 'main.js');
22
+ }
23
+ }
24
+ export const paths = new Paths();
@@ -0,0 +1,21 @@
1
+ {
2
+ "$schema": "http://json-schema.org/draft-07/schema#",
3
+ "type": "object",
4
+ "$defs": {
5
+ "alias": {
6
+ "oneOf": [
7
+ { "type": "string" },
8
+ { "type": "array", "items": { "type": "string" } }
9
+ ]
10
+ },
11
+ "NestedStringObject": {
12
+ "type": "object",
13
+ "additionalProperties": {
14
+ "oneOf": [
15
+ { "$ref": "#/$defs/NestedStringObject" },
16
+ { "type": "string" }
17
+ ]
18
+ }
19
+ }
20
+ }
21
+ }