@genesislcap/build-kit 14.162.2-alpha-a2fd6f2.0 → 14.162.2
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.
- package/dist/utils/index.d.ts +0 -1
- package/dist/utils/index.d.ts.map +1 -1
- package/dist/utils/index.js +0 -1
- package/dist/utils/prompts.js +1 -1
- package/dist/utils/seed.processing.d.ts.map +1 -1
- package/dist/utils/seed.processing.js +3 -3
- package/package.json +3 -3
- package/src/index.ts +2 -0
- package/src/types/build-context.ts +57 -0
- package/src/types/config.ts +269 -0
- package/src/types/index.ts +3 -0
- package/src/types/pwa.ts +67 -0
- package/src/utils/archive.ts +18 -0
- package/src/utils/args.ts +7 -0
- package/src/utils/build-context.ts +42 -0
- package/src/utils/child-process.ts +115 -0
- package/src/utils/config.ts +45 -0
- package/src/utils/console.ts +1 -0
- package/src/utils/env.ts +71 -0
- package/src/utils/federation.ts +40 -0
- package/src/utils/fs.ts +53 -0
- package/src/utils/handlebars.ts +61 -0
- package/src/utils/https.ts +29 -0
- package/src/utils/index.ts +21 -0
- package/src/utils/json.ts +12 -0
- package/src/utils/npm.ts +155 -0
- package/src/utils/object.ts +21 -0
- package/src/utils/prompts.ts +160 -0
- package/src/utils/proxy.ts +139 -0
- package/src/utils/seed.fetching.ts +257 -0
- package/src/utils/seed.meta.ts +34 -0
- package/src/utils/seed.processing.ts +100 -0
- package/src/utils/seed.setup.ts +101 -0
- package/src/utils/time.ts +1 -0
- package/test/utils/args.test.ts +17 -0
- package/test/utils/build-context.test.ts +68 -0
- package/test/utils/env.test.ts +212 -0
- package/test/utils/handlebars.test.ts +277 -0
- package/test/utils/https.test.ts +14 -0
- package/test/utils/proxy.test.ts +96 -0
- package/test/utils/seed.fetching.test.ts +145 -0
- package/test/utils/seed.meta.test.ts +33 -0
- package/tsconfig.json +18 -0
- package/dist/utils/seed.commands.d.ts +0 -19
- package/dist/utils/seed.commands.d.ts.map +0 -1
- package/dist/utils/seed.commands.js +0 -58
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import defu from 'defu';
|
|
2
|
+
import type { PackageJson } from 'pkg-types';
|
|
3
|
+
import { resolveFederationConfig, getFederatedPkgName } from './federation';
|
|
4
|
+
import { tryRequire } from './npm';
|
|
5
|
+
|
|
6
|
+
const getDefaultConfig = (packageName) => ({
|
|
7
|
+
app: {},
|
|
8
|
+
builder: {
|
|
9
|
+
default: 'webpack',
|
|
10
|
+
dev: 'vite',
|
|
11
|
+
lint: 'eslint-stylelint',
|
|
12
|
+
test: 'uvu-playwright',
|
|
13
|
+
},
|
|
14
|
+
clean: !process.env.GENX_NO_CLEAN,
|
|
15
|
+
copyAssets: ['**/*.{css,jpg,png,svg,mp4,ogg,webm}'],
|
|
16
|
+
env: [
|
|
17
|
+
'API_HOST',
|
|
18
|
+
'DEFAULT_ORGANISATION',
|
|
19
|
+
'DEFAULT_USER',
|
|
20
|
+
'DEFAULT_PASSWORD',
|
|
21
|
+
'ENABLE_SSO',
|
|
22
|
+
'GENX.*',
|
|
23
|
+
'GENX_*',
|
|
24
|
+
'FORCE_HTTP',
|
|
25
|
+
'SOCKET_EXT',
|
|
26
|
+
],
|
|
27
|
+
federation: {
|
|
28
|
+
name: getFederatedPkgName(packageName),
|
|
29
|
+
},
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
const readConfig = (packageJSON: PackageJson, path = './genx.config') => {
|
|
33
|
+
const genxConfig = tryRequire(path) || {};
|
|
34
|
+
const packageJSONConfig = packageJSON.genx || {};
|
|
35
|
+
const defaultConfig = getDefaultConfig(packageJSON.name);
|
|
36
|
+
return defu(genxConfig, packageJSONConfig, defaultConfig);
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
export const loadConfig = async (packageJSON: PackageJson) => {
|
|
40
|
+
const config = readConfig(packageJSON);
|
|
41
|
+
return {
|
|
42
|
+
...config,
|
|
43
|
+
federation: await resolveFederationConfig(config.federation, packageJSON),
|
|
44
|
+
};
|
|
45
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { bold, dim, green, blue, white, yellow, grey, italic } from 'chalk';
|
package/src/utils/env.ts
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import consola from 'consola';
|
|
2
|
+
import type { BuildContext, ENV } from '../types';
|
|
3
|
+
|
|
4
|
+
export const setNodeEnv = (targetEnv: ENV) => {
|
|
5
|
+
const currentEnv = process.env.NODE_ENV;
|
|
6
|
+
if (currentEnv && currentEnv !== targetEnv) {
|
|
7
|
+
consola.warn(`Changing \`NODE_ENV\` from \`${currentEnv}\` to \`${targetEnv}\`.`);
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
process.env.NODE_ENV = targetEnv;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
const resolveEnvValue = (ctx: BuildContext, key: string, value: string) => {
|
|
14
|
+
// `-e `FOO` case
|
|
15
|
+
// 1. check package.json config
|
|
16
|
+
// 2. fall back to a truthy value (shortcut for setting env var flags where exact value isn't important as long as it's truthy e.g. -e CI)
|
|
17
|
+
// we don't check process.env because there it'd mean setting env variable to the same value it already has
|
|
18
|
+
if (!value) {
|
|
19
|
+
return ctx.pkg.config && ctx.pkg.config[key] !== undefined ? ctx.pkg.config[key] : '1';
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// `-e FOO=ABC` case
|
|
23
|
+
// 1. check package.json config
|
|
24
|
+
if (ctx.pkg.config && ctx.pkg.config[value] !== undefined) {
|
|
25
|
+
return ctx.pkg.config[value];
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// 2. check process.env
|
|
29
|
+
if (process.env[value] !== undefined) {
|
|
30
|
+
return process.env[value];
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// 3. fall back to provided value
|
|
34
|
+
return value;
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
export const setEnvVars = (ctx: BuildContext) => {
|
|
38
|
+
const { env = '' } = ctx.cli.options;
|
|
39
|
+
const envCombined = Array.isArray(env) ? env.join(',') : env;
|
|
40
|
+
const vars = envCombined.split(',');
|
|
41
|
+
vars.forEach((v) => {
|
|
42
|
+
const [keyRaw, valueRaw] = v.split('=');
|
|
43
|
+
const key = (keyRaw || '').trim();
|
|
44
|
+
const value = (valueRaw || '').trim();
|
|
45
|
+
if (key) {
|
|
46
|
+
const resolved = resolveEnvValue(ctx, key, value);
|
|
47
|
+
consola.info(`Setting \`${key}\` env variable to \`${resolved}\`.`);
|
|
48
|
+
process.env[key] = resolved;
|
|
49
|
+
}
|
|
50
|
+
});
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
// Given env variable name patterns and HTTP config, return a define plugin config
|
|
54
|
+
export const resolveDefineConfig = (
|
|
55
|
+
patterns: string[],
|
|
56
|
+
httpConfig?: object,
|
|
57
|
+
builder?: 'webpack' | 'vite',
|
|
58
|
+
): Record<string, string> =>
|
|
59
|
+
Object.entries(process.env)
|
|
60
|
+
.filter(([key]) => patterns.some((pattern) => key.match(pattern)))
|
|
61
|
+
.reduce(
|
|
62
|
+
(acc, [key, value]) => {
|
|
63
|
+
acc[key] = JSON.stringify(value);
|
|
64
|
+
return acc;
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
NODE_ENV: JSON.stringify(process.env.NODE_ENV),
|
|
68
|
+
HTTP_CONFIG: JSON.stringify(httpConfig || ''),
|
|
69
|
+
BUILDER: JSON.stringify(builder ?? ''),
|
|
70
|
+
},
|
|
71
|
+
);
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { camelCase } from 'change-case';
|
|
2
|
+
import type { PackageJson } from 'pkg-types';
|
|
3
|
+
import { loadPackageJSONFiles } from './npm';
|
|
4
|
+
|
|
5
|
+
export const getFederatedPkgName = (pkgName = '') => camelCase(pkgName.split('/').pop());
|
|
6
|
+
|
|
7
|
+
const getRemoteEntry = (name, port, host = 'localhost') =>
|
|
8
|
+
`${name}@http://${host}:${port}/remoteEntry.js`;
|
|
9
|
+
|
|
10
|
+
const resolveFederatedRemotes = async (packages = []) => {
|
|
11
|
+
const packageJSONFiles = await loadPackageJSONFiles(packages);
|
|
12
|
+
return (await Promise.all(packageJSONFiles)).reduce((acc, packageJSON) => {
|
|
13
|
+
const name = getFederatedPkgName(packageJSON.name);
|
|
14
|
+
const port = packageJSON.config?.PORT;
|
|
15
|
+
acc[name] = getRemoteEntry(name, port);
|
|
16
|
+
return acc;
|
|
17
|
+
}, {});
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
const resolveSharedRemotes = (packages = [], packageJSON: PackageJson) =>
|
|
21
|
+
packages.reduce((acc, p) => {
|
|
22
|
+
acc[p] = {
|
|
23
|
+
singleton: true,
|
|
24
|
+
requiredVersion: packageJSON.dependencies[p],
|
|
25
|
+
};
|
|
26
|
+
return acc;
|
|
27
|
+
}, {});
|
|
28
|
+
|
|
29
|
+
export const resolveFederationConfig = async (
|
|
30
|
+
federation = { remotes: [], shared: [] },
|
|
31
|
+
packageJSON: PackageJson,
|
|
32
|
+
) => {
|
|
33
|
+
const remotes = await resolveFederatedRemotes(federation.remotes);
|
|
34
|
+
const shared = resolveSharedRemotes(federation.shared, packageJSON);
|
|
35
|
+
return {
|
|
36
|
+
...federation,
|
|
37
|
+
remotes,
|
|
38
|
+
shared,
|
|
39
|
+
};
|
|
40
|
+
};
|
package/src/utils/fs.ts
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import type { PathLike, Stats } from 'node:fs';
|
|
2
|
+
import { readdirSync, statSync, renameSync } from 'node:fs';
|
|
3
|
+
import { basename, dirname, extname, resolve } from 'node:path';
|
|
4
|
+
import { rimraf } from 'rimraf';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Deletes one or more provided paths
|
|
8
|
+
*/
|
|
9
|
+
export const rm = async (paths: string | string[]) =>
|
|
10
|
+
// We can add support for globbing or filtering in the future
|
|
11
|
+
// See https://github.com/isaacs/rimraf#rimraff-opts---promise for all options
|
|
12
|
+
rimraf(paths);
|
|
13
|
+
|
|
14
|
+
export const rename = (oldPath: PathLike, newPath: PathLike) => renameSync(oldPath, newPath);
|
|
15
|
+
|
|
16
|
+
export interface fsEntry {
|
|
17
|
+
path: string; // full path
|
|
18
|
+
base: string; // last portion of a path, similar to the Unix basename command
|
|
19
|
+
ext: string; // file extension, if present
|
|
20
|
+
dir: string; // directory name of a path, similar to the Unix dirname command
|
|
21
|
+
isDir: boolean; // whether entry is a directory
|
|
22
|
+
stats: Stats; // Stats object
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export const listAllFiles = (
|
|
26
|
+
startPath,
|
|
27
|
+
filter = (item: fsEntry) => true,
|
|
28
|
+
mapper = (item: fsEntry): fsEntry => item,
|
|
29
|
+
): string[] => {
|
|
30
|
+
const filenames = new Set<string>();
|
|
31
|
+
const walk = (dirPath) => {
|
|
32
|
+
const files = readdirSync(dirPath);
|
|
33
|
+
for (const file of files) {
|
|
34
|
+
const fullPath = resolve(dirPath, file);
|
|
35
|
+
const stats = statSync(fullPath);
|
|
36
|
+
const isDir = stats.isDirectory();
|
|
37
|
+
const base = basename(fullPath);
|
|
38
|
+
const ext = isDir ? null : extname(fullPath).toLowerCase();
|
|
39
|
+
const dir = dirname(fullPath);
|
|
40
|
+
const entry = mapper({ path: fullPath, isDir, base, ext, dir, stats });
|
|
41
|
+
|
|
42
|
+
if (filter(entry)) {
|
|
43
|
+
if (isDir) {
|
|
44
|
+
walk(entry.path);
|
|
45
|
+
} else {
|
|
46
|
+
filenames.add(entry.path);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
walk(startPath);
|
|
52
|
+
return [...filenames];
|
|
53
|
+
};
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { PathOrFileDescriptor, readFileSync, writeFileSync } from 'node:fs';
|
|
2
|
+
import {
|
|
3
|
+
camelCase,
|
|
4
|
+
capitalCase,
|
|
5
|
+
constantCase,
|
|
6
|
+
dotCase,
|
|
7
|
+
paramCase,
|
|
8
|
+
pascalCase,
|
|
9
|
+
pathCase,
|
|
10
|
+
sentenceCase,
|
|
11
|
+
snakeCase,
|
|
12
|
+
} from 'change-case';
|
|
13
|
+
import handlebars from 'handlebars';
|
|
14
|
+
|
|
15
|
+
/*
|
|
16
|
+
| Method | Result |
|
|
17
|
+
| ----------------- | ----------- |
|
|
18
|
+
| `camelCase` | `twoWords` |
|
|
19
|
+
| `capitalCase` | `Two Words` |
|
|
20
|
+
| `constantCase` | `TWO_WORDS` |
|
|
21
|
+
| `dotCase` | `two.words` |
|
|
22
|
+
| `kebabCase` | `two-words` |
|
|
23
|
+
| `lowerCase` | `two words` |
|
|
24
|
+
| `pascalCase` | `TwoWords` |
|
|
25
|
+
| `pathCase` | `two/words` |
|
|
26
|
+
| `sentenceCase` | `Two words` |
|
|
27
|
+
| `snakeCase` | `two_words` |
|
|
28
|
+
| `upperCase` | `TWO_WORDS` |
|
|
29
|
+
*/
|
|
30
|
+
|
|
31
|
+
handlebars.registerHelper('camelCase', (s) => s && camelCase(s));
|
|
32
|
+
handlebars.registerHelper('capitalCase', (s) => s && capitalCase(s));
|
|
33
|
+
handlebars.registerHelper('constantCase', (s) => s && constantCase(s));
|
|
34
|
+
handlebars.registerHelper('dotCase', (s) => s && dotCase(s));
|
|
35
|
+
handlebars.registerHelper('kebabCase', (s) => s && paramCase(s));
|
|
36
|
+
handlebars.registerHelper('lowerCase', (s) => s && s.toLowerCase());
|
|
37
|
+
handlebars.registerHelper('pascalCase', (s) => s && pascalCase(s));
|
|
38
|
+
handlebars.registerHelper('pathCase', (s) => s && pathCase(s));
|
|
39
|
+
handlebars.registerHelper('sentenceCase', (s) => s && sentenceCase(s));
|
|
40
|
+
handlebars.registerHelper('snakeCase', (s) => s && snakeCase(s));
|
|
41
|
+
handlebars.registerHelper('upperCase', (s) => s && s.toUpperCase());
|
|
42
|
+
handlebars.registerHelper('ifEquals', (arg1, arg2, options) => {
|
|
43
|
+
return arg1 === arg2 ? options.fn(this) : options.inverse(this);
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
export const render = (template, data) => handlebars.compile(template)(data);
|
|
47
|
+
|
|
48
|
+
export const renderFile = (
|
|
49
|
+
file: PathOrFileDescriptor,
|
|
50
|
+
data: object,
|
|
51
|
+
templateFile?: PathOrFileDescriptor,
|
|
52
|
+
) => {
|
|
53
|
+
const template = readFileSync(templateFile || file, 'utf8');
|
|
54
|
+
const output = render(template, data);
|
|
55
|
+
writeFileSync(file, output, 'utf8');
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
export const registerPartial = (name: string, templateFile) => {
|
|
59
|
+
const template = readFileSync(templateFile, 'utf8');
|
|
60
|
+
handlebars.registerPartial(name, template);
|
|
61
|
+
};
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
2
|
+
import { resolve } from 'node:path';
|
|
3
|
+
import consola from 'consola';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* @public
|
|
7
|
+
*/
|
|
8
|
+
export type DevCertOptions = {
|
|
9
|
+
cert: Buffer;
|
|
10
|
+
key: Buffer;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Get default dev cert options.
|
|
15
|
+
*
|
|
16
|
+
* @public
|
|
17
|
+
*/
|
|
18
|
+
export const getDevCertOptions = (): DevCertOptions | undefined => {
|
|
19
|
+
const certDir = resolve(__dirname, '../../cert/');
|
|
20
|
+
const cert = resolve(certDir, 'localhost.crt');
|
|
21
|
+
const key = resolve(certDir, 'localhost.key');
|
|
22
|
+
if (existsSync(cert) && existsSync(key)) {
|
|
23
|
+
return {
|
|
24
|
+
cert: readFileSync(cert),
|
|
25
|
+
key: readFileSync(key),
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
consola.warn(`Could not load the default development certificates from ${certDir}.`);
|
|
29
|
+
};
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export * from './args';
|
|
2
|
+
export * from './archive';
|
|
3
|
+
export * from './build-context';
|
|
4
|
+
export * from './child-process';
|
|
5
|
+
export * from './config';
|
|
6
|
+
export * from './console';
|
|
7
|
+
export * from './env';
|
|
8
|
+
export * from './federation';
|
|
9
|
+
export * from './fs';
|
|
10
|
+
export * from './handlebars';
|
|
11
|
+
export * from './https';
|
|
12
|
+
export * from './json';
|
|
13
|
+
export * from './npm';
|
|
14
|
+
export * from './prompts';
|
|
15
|
+
export * from './proxy';
|
|
16
|
+
export * from './object';
|
|
17
|
+
export * from './seed.fetching';
|
|
18
|
+
export * from './seed.meta';
|
|
19
|
+
export * from './seed.processing';
|
|
20
|
+
export * from './seed.setup';
|
|
21
|
+
export * from './time';
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { writeFileSync } from 'node:fs';
|
|
2
|
+
import consola from 'consola';
|
|
3
|
+
|
|
4
|
+
export { default as editJSONFile } from 'edit-json-file';
|
|
5
|
+
|
|
6
|
+
export const writeJSON = (path, data) => {
|
|
7
|
+
try {
|
|
8
|
+
writeFileSync(path, JSON.stringify(data, null, 2));
|
|
9
|
+
} catch (e) {
|
|
10
|
+
consola.error(`Error writing to ${path}`);
|
|
11
|
+
}
|
|
12
|
+
};
|
package/src/utils/npm.ts
ADDED
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
import { execSync } from 'node:child_process';
|
|
2
|
+
import { writeFileSync } from 'node:fs';
|
|
3
|
+
import { resolve, dirname } from 'node:path';
|
|
4
|
+
import consola from 'consola';
|
|
5
|
+
import jiti from 'jiti';
|
|
6
|
+
import { readPackageJSON, resolvePackageJSON } from 'pkg-types';
|
|
7
|
+
import type { PackageJson } from 'pkg-types';
|
|
8
|
+
import type { BuildContext } from '../types';
|
|
9
|
+
|
|
10
|
+
// Attempt to load id.{js,json,ts} file
|
|
11
|
+
export const tryRequire = (id, rootDir = process.cwd()) => {
|
|
12
|
+
const _require = jiti(rootDir, { interopDefault: true, esmResolve: true });
|
|
13
|
+
try {
|
|
14
|
+
return _require(id);
|
|
15
|
+
} catch (error) {
|
|
16
|
+
if (error.code !== 'MODULE_NOT_FOUND') {
|
|
17
|
+
consola.error(`Error trying import ${id} from ${rootDir}`, error);
|
|
18
|
+
}
|
|
19
|
+
return null;
|
|
20
|
+
}
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
export const resolveBin = async (bin: string, module: string = bin) => {
|
|
24
|
+
const modulePath = require.resolve(module);
|
|
25
|
+
const dir = dirname(await resolvePackageJSON(modulePath));
|
|
26
|
+
const json = await readPackageJSON(modulePath);
|
|
27
|
+
const path = typeof json.bin === 'string' ? json.bin : (json.bin as Record<string, string>)[bin];
|
|
28
|
+
if (!path || !dir) {
|
|
29
|
+
throw new Error(`Could not resolve ${bin} / ${module}: ${dir} ${path}`);
|
|
30
|
+
}
|
|
31
|
+
return resolve(dir, path);
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
export const loadPackageJSON = async (dir?: string): Promise<PackageJson> => {
|
|
35
|
+
const pkg = (await readPackageJSON(dir)) || {};
|
|
36
|
+
const nameParts = (pkg.name || '').split('/');
|
|
37
|
+
// @scope/name -> name
|
|
38
|
+
// name -> name
|
|
39
|
+
pkg.nameUnscoped = nameParts.pop();
|
|
40
|
+
// @scope/name -> @scope
|
|
41
|
+
// name -> undefined
|
|
42
|
+
pkg.nameScope = nameParts.shift();
|
|
43
|
+
return pkg;
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
export const loadPackageJSONFiles = async (packages = []): Promise<PackageJson[]> =>
|
|
47
|
+
Promise.all(
|
|
48
|
+
packages.map((p) => {
|
|
49
|
+
const dir = dirname(require.resolve(p));
|
|
50
|
+
return loadPackageJSON(dir);
|
|
51
|
+
}),
|
|
52
|
+
);
|
|
53
|
+
|
|
54
|
+
/*
|
|
55
|
+
Given package name and range (e.g. '^14.0.1', '~14.0.1' or 'latest', return latest matching version)
|
|
56
|
+
|
|
57
|
+
Registry response varies depending on whether a single or multiple versions are available.
|
|
58
|
+
We return the single version which is the latest available.
|
|
59
|
+
|
|
60
|
+
Response if a single version is available:
|
|
61
|
+
'14.41.1'
|
|
62
|
+
|
|
63
|
+
Response if multiple versions available:
|
|
64
|
+
@genesislcap/genx@14.41.0 '14.41.0'
|
|
65
|
+
@genesislcap/genx@14.41.1 '14.41.1'
|
|
66
|
+
*/
|
|
67
|
+
export const getLatestVersion = (name, range = 'latest', verbose = true) => {
|
|
68
|
+
let result;
|
|
69
|
+
|
|
70
|
+
try {
|
|
71
|
+
result = execSync(`npm info ${name}@${range} version`, {
|
|
72
|
+
stdio: ['pipe', 'pipe', 'ignore'],
|
|
73
|
+
})
|
|
74
|
+
.toString('utf8')
|
|
75
|
+
.trim()
|
|
76
|
+
.split('\n')
|
|
77
|
+
.pop()
|
|
78
|
+
.split(' ')
|
|
79
|
+
.pop()
|
|
80
|
+
.replace(/'/g, '');
|
|
81
|
+
} catch (e) {
|
|
82
|
+
if (verbose) {
|
|
83
|
+
consola.error(`Could not fetch ${name} version ${range} information from the registry`);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
return result;
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
// given package version (^14.0.0, ~14.0.0), return range specifier (^, ~)
|
|
91
|
+
export const getRangeSpecifier = (version) => {
|
|
92
|
+
const match = version.toString().match(/^[><=^~*]+/);
|
|
93
|
+
return (match && match[0]) || '';
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
// Return package.json without additional fields added by us
|
|
97
|
+
export const getRawPkg = (ctx: BuildContext) => {
|
|
98
|
+
const { nameUnscoped, nameScope, ...pkg } = ctx.pkg;
|
|
99
|
+
return pkg;
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
export const updatePackageJSON = async (pkg: PackageJson) => {
|
|
103
|
+
const pkgJSONPath = await resolvePackageJSON();
|
|
104
|
+
try {
|
|
105
|
+
writeFileSync(pkgJSONPath, `${JSON.stringify(pkg, null, 2)}\n`);
|
|
106
|
+
} catch (e) {
|
|
107
|
+
consola.error(`Error updating package.json: ${pkgJSONPath}`, e);
|
|
108
|
+
}
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
const updateVersionType = (
|
|
112
|
+
deps: Record<string, string>,
|
|
113
|
+
newVersion: string,
|
|
114
|
+
exclude: string[],
|
|
115
|
+
include: string[] = ['@genesislcap'],
|
|
116
|
+
): { name: string; to: string; from: string }[] => {
|
|
117
|
+
const updates = [];
|
|
118
|
+
|
|
119
|
+
Object.entries(deps || {}).forEach(([name, version = '']) => {
|
|
120
|
+
if (
|
|
121
|
+
include.some((pattern) => name.includes(pattern)) &&
|
|
122
|
+
exclude.every((pattern) => !name.includes(pattern))
|
|
123
|
+
) {
|
|
124
|
+
const available = newVersion || getLatestVersion(name, version);
|
|
125
|
+
if (!available) {
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
const rangeSpecifier = getRangeSpecifier(version);
|
|
129
|
+
const versionNew = `${rangeSpecifier}${available}`;
|
|
130
|
+
|
|
131
|
+
if (version !== versionNew) {
|
|
132
|
+
updates.push({
|
|
133
|
+
name,
|
|
134
|
+
from: version,
|
|
135
|
+
to: versionNew,
|
|
136
|
+
});
|
|
137
|
+
deps[name] = versionNew;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
return updates;
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
export const updateVersions = (
|
|
146
|
+
pkg: PackageJson,
|
|
147
|
+
newVersion: string,
|
|
148
|
+
exclude: string[],
|
|
149
|
+
include: string[] = ['@genesislcap'],
|
|
150
|
+
) => {
|
|
151
|
+
const deps = updateVersionType(pkg.dependencies, newVersion, exclude, include);
|
|
152
|
+
const devDeps = updateVersionType(pkg.devDependencies, newVersion, exclude, include);
|
|
153
|
+
const peerDeps = updateVersionType(pkg.peerDependencies, newVersion, exclude, include);
|
|
154
|
+
return [...deps, ...devDeps, ...peerDeps];
|
|
155
|
+
};
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { snakeCase } from 'change-case';
|
|
2
|
+
|
|
3
|
+
export const objToSnakeCase = (obj: unknown) =>
|
|
4
|
+
Object.keys(obj).reduce((acc, key) => {
|
|
5
|
+
const keyRenamed = snakeCase(key);
|
|
6
|
+
acc[keyRenamed] = obj[key];
|
|
7
|
+
return acc;
|
|
8
|
+
}, {});
|
|
9
|
+
|
|
10
|
+
export const replaceObjectKeys = (input, searchValue, replaceValue) =>
|
|
11
|
+
Object.fromEntries(
|
|
12
|
+
Object.entries(input).map(([key, value]) => [key.replace(searchValue, replaceValue), value]),
|
|
13
|
+
);
|
|
14
|
+
|
|
15
|
+
export const excludeObjKeys = (obj: unknown, excludeKeys: string[]) =>
|
|
16
|
+
Object.keys(obj).reduce((acc, key) => {
|
|
17
|
+
if (!excludeKeys.includes(key)) {
|
|
18
|
+
acc[key] = obj[key];
|
|
19
|
+
}
|
|
20
|
+
return acc;
|
|
21
|
+
}, {});
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
import { basename } from 'node:path';
|
|
2
|
+
import consola from 'consola';
|
|
3
|
+
import inquirer from 'inquirer';
|
|
4
|
+
import type { Question } from 'inquirer';
|
|
5
|
+
import { bold } from './console';
|
|
6
|
+
|
|
7
|
+
export const runPrompts = async (prompts: any, prevAns: any = {}) => prompts(inquirer, prevAns);
|
|
8
|
+
|
|
9
|
+
export const validateSeedData = (data, validators) => {
|
|
10
|
+
const errors = [];
|
|
11
|
+
|
|
12
|
+
Object.keys(data).forEach((key) => {
|
|
13
|
+
const value = data[key];
|
|
14
|
+
if (typeof value === 'undefined') {
|
|
15
|
+
errors.push({ message: `${key} is undefined` });
|
|
16
|
+
} else if (value === '') {
|
|
17
|
+
errors.push({ message: `${key} is empty` });
|
|
18
|
+
} else if (validators[key]) {
|
|
19
|
+
const isValid = validators[key](value);
|
|
20
|
+
if (isValid !== true) {
|
|
21
|
+
errors.push({ message: `${key} is invalid: ${bold(value)} ${isValid}` });
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
return errors;
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
const getPromptInputs = async (prompts): Promise<Question[]> => {
|
|
30
|
+
// disable any prompt messages
|
|
31
|
+
const _log = console.log;
|
|
32
|
+
console.log = () => {};
|
|
33
|
+
|
|
34
|
+
const inputs = [];
|
|
35
|
+
|
|
36
|
+
const mockInquirer = {
|
|
37
|
+
prompt: async (promptInputs) => {
|
|
38
|
+
inputs.push(...promptInputs);
|
|
39
|
+
return {};
|
|
40
|
+
},
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
await prompts(mockInquirer);
|
|
44
|
+
|
|
45
|
+
// re-enable prompt messages
|
|
46
|
+
console.log = _log;
|
|
47
|
+
|
|
48
|
+
return inputs;
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
export const getPromptDefaults = async (prompts) => {
|
|
52
|
+
const inputs = await getPromptInputs(prompts);
|
|
53
|
+
|
|
54
|
+
const defaults = inputs.reduce((acc, value) => {
|
|
55
|
+
let addDefault;
|
|
56
|
+
if (typeof value.when === 'function') {
|
|
57
|
+
addDefault = value.when(acc);
|
|
58
|
+
} else if (typeof value.when === 'undefined') {
|
|
59
|
+
addDefault = true;
|
|
60
|
+
} else {
|
|
61
|
+
addDefault = value.when;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
if (addDefault) {
|
|
65
|
+
acc[value.name] = value.default;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
return acc;
|
|
69
|
+
}, {});
|
|
70
|
+
|
|
71
|
+
return defaults;
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
export const getPromptValidators = async (prompts) => {
|
|
75
|
+
const inputs = await getPromptInputs(prompts);
|
|
76
|
+
|
|
77
|
+
const validators = inputs.reduce((acc, value) => {
|
|
78
|
+
acc[value.name] = value.validate;
|
|
79
|
+
return acc;
|
|
80
|
+
}, {});
|
|
81
|
+
|
|
82
|
+
return validators;
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
// process CLI prompt params
|
|
86
|
+
const getCLIParams = (
|
|
87
|
+
persistedAnswers,
|
|
88
|
+
params,
|
|
89
|
+
defaults,
|
|
90
|
+
dir,
|
|
91
|
+
subdir,
|
|
92
|
+
skipOptionalPrompts,
|
|
93
|
+
seedMeta,
|
|
94
|
+
details,
|
|
95
|
+
validate,
|
|
96
|
+
) => {
|
|
97
|
+
const base = skipOptionalPrompts ? defaults : {};
|
|
98
|
+
|
|
99
|
+
const cliParams = {
|
|
100
|
+
...base,
|
|
101
|
+
...persistedAnswers,
|
|
102
|
+
...params,
|
|
103
|
+
appName: basename(dir),
|
|
104
|
+
pkgName: basename(dir),
|
|
105
|
+
directory: basename(dir),
|
|
106
|
+
appSeed: {
|
|
107
|
+
type: seedMeta.local ? 'local' : 'remote',
|
|
108
|
+
name: seedMeta.seed,
|
|
109
|
+
description: details.description,
|
|
110
|
+
version: details.version,
|
|
111
|
+
repo: seedMeta.repo,
|
|
112
|
+
artifact: seedMeta.artifact,
|
|
113
|
+
ref: seedMeta.ref,
|
|
114
|
+
subdir: seedMeta.subdir,
|
|
115
|
+
date: new Date(),
|
|
116
|
+
},
|
|
117
|
+
};
|
|
118
|
+
const validationErrors = validate(cliParams);
|
|
119
|
+
if (validationErrors.length) {
|
|
120
|
+
consola.error('Validation errors detected:');
|
|
121
|
+
validationErrors.forEach((error) => consola.log(` * ${error.message}`));
|
|
122
|
+
process.exit(1);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
return cliParams;
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
// get combined prompt answers (CLI params / defaults / user interaction)
|
|
129
|
+
export const getData = async (
|
|
130
|
+
persistedAnswers,
|
|
131
|
+
prompts,
|
|
132
|
+
details,
|
|
133
|
+
defaults,
|
|
134
|
+
validate,
|
|
135
|
+
params,
|
|
136
|
+
dir,
|
|
137
|
+
skipOptionalPrompts,
|
|
138
|
+
seedMeta,
|
|
139
|
+
) => {
|
|
140
|
+
// process CLI params
|
|
141
|
+
const cliParams = getCLIParams(
|
|
142
|
+
persistedAnswers,
|
|
143
|
+
params,
|
|
144
|
+
defaults,
|
|
145
|
+
dir,
|
|
146
|
+
seedMeta.subdir,
|
|
147
|
+
skipOptionalPrompts,
|
|
148
|
+
seedMeta,
|
|
149
|
+
details,
|
|
150
|
+
validate,
|
|
151
|
+
);
|
|
152
|
+
|
|
153
|
+
const data = await runPrompts(prompts, cliParams);
|
|
154
|
+
|
|
155
|
+
const combined = {
|
|
156
|
+
...cliParams,
|
|
157
|
+
...data,
|
|
158
|
+
};
|
|
159
|
+
return combined;
|
|
160
|
+
};
|