@jungvonmatt/contentful-ssg 1.0.2 → 1.1.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.
- package/dist/cli.js +12 -1
- package/dist/lib/config.d.ts +1 -1
- package/dist/lib/config.js +14 -7
- package/dist/lib/file-manager.js +10 -2
- package/dist/lib/object.d.ts +2 -1
- package/dist/lib/object.js +8 -3
- package/dist/types.d.ts +3 -0
- package/package.json +7 -2
- package/src/cli.ts +29 -3
- package/src/lib/config.ts +38 -18
- package/src/lib/file-manager.ts +14 -2
- package/src/lib/object.test.ts +16 -1
- package/src/lib/object.ts +14 -3
- package/src/types.ts +4 -0
package/dist/cli.js
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import path from 'path';
|
|
3
3
|
import chalk from 'chalk';
|
|
4
4
|
import { existsSync } from 'fs';
|
|
5
|
+
import { readFile } from 'fs/promises';
|
|
5
6
|
import { outputFile } from 'fs-extra';
|
|
6
7
|
import prettier from 'prettier';
|
|
7
8
|
import { Command } from 'commander';
|
|
@@ -44,7 +45,17 @@ program
|
|
|
44
45
|
if (verified.directory?.startsWith('/')) {
|
|
45
46
|
verified.directory = path.relative(process.cwd(), verified.directory);
|
|
46
47
|
}
|
|
47
|
-
const environmentKeys = Object.keys(environmentConfig)
|
|
48
|
+
const environmentKeys = Object.keys(environmentConfig);
|
|
49
|
+
if (environmentConfig && existsSync('.env')) {
|
|
50
|
+
const envSource = await readFile('.env', 'utf8');
|
|
51
|
+
const nextEnvSource = envSource
|
|
52
|
+
.replace(/(CONTENTFUL_SPACE_ID\s*=\s*['"]?)[^'"]*(['"]?)/, `$1${verified.spaceId}$2`)
|
|
53
|
+
.replace(/(CONTENTFUL_ENVIRONMENT_ID\s*=\s*['"]?)[^'"]*(['"]?)/, `$1${verified.environmentId}$2`)
|
|
54
|
+
.replace(/(CONTENTFUL_MANAGEMENT_TOKEN\s*=\s*['"]?)[^'"]*(['"]?)/, `$1${verified.managementToken}$2`)
|
|
55
|
+
.replace(/(CONTENTFUL_PREVIEW_TOKEN\s*=\s*['"]?)[^'"]*(['"]?)/, `$1${verified.previewAccessToken}$2`)
|
|
56
|
+
.replace(/(CONTENTFUL_DELIVERY_TOKEN\s*=\s*['"]?)[^'"]*(['"]?)/, `$1${verified.accessToken}$2`);
|
|
57
|
+
await outputFile('.env', nextEnvSource);
|
|
58
|
+
}
|
|
48
59
|
const cleanedConfig = omitKeys(verified, 'preview', 'verbose', 'rootDir', 'resolvedPlugins', 'host', 'managementToken', ...environmentKeys);
|
|
49
60
|
let content = '';
|
|
50
61
|
if (useTypescript) {
|
package/dist/lib/config.d.ts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
import type { Config, ContentfulConfig } from '../types.js';
|
|
2
|
-
export declare const getEnvironmentConfig: () => ContentfulConfig;
|
|
2
|
+
export declare const getEnvironmentConfig: (strict?: boolean) => ContentfulConfig;
|
|
3
3
|
export declare const getConfig: (args?: Partial<Config>) => Promise<Config>;
|
package/dist/lib/config.js
CHANGED
|
@@ -4,6 +4,7 @@ import { cosmiconfig } from 'cosmiconfig';
|
|
|
4
4
|
import mergeOptionsModule from 'merge-options';
|
|
5
5
|
import { dirname, isAbsolute, resolve } from 'path';
|
|
6
6
|
import slash from 'slash';
|
|
7
|
+
import { reduceAsync } from './array.js';
|
|
7
8
|
import { createRequire } from './create-require.js';
|
|
8
9
|
import { isObject, removeEmpty } from './object.js';
|
|
9
10
|
const typescriptLoader = async (filePath) => {
|
|
@@ -72,33 +73,34 @@ const loadConfig = async (moduleName) => {
|
|
|
72
73
|
});
|
|
73
74
|
return explorer.search();
|
|
74
75
|
};
|
|
75
|
-
export const getEnvironmentConfig = () => removeEmpty({
|
|
76
|
+
export const getEnvironmentConfig = (strict = true) => removeEmpty({
|
|
76
77
|
spaceId: process.env.CONTENTFUL_SPACE_ID,
|
|
77
78
|
environmentId: process.env.CONTENTFUL_ENVIRONMENT_ID,
|
|
78
79
|
managementToken: process.env.CONTENTFUL_MANAGEMENT_TOKEN,
|
|
79
80
|
previewAccessToken: process.env.CONTENTFUL_PREVIEW_TOKEN,
|
|
80
81
|
accessToken: process.env.CONTENTFUL_DELIVERY_TOKEN,
|
|
81
|
-
});
|
|
82
|
+
}, strict);
|
|
82
83
|
export const getConfig = async (args) => {
|
|
83
84
|
const defaultOptions = {
|
|
84
85
|
environmentId: 'master',
|
|
85
86
|
host: 'api.contentful.com',
|
|
86
87
|
directory: resolve(process.cwd(), 'content'),
|
|
88
|
+
managedDirectories: [],
|
|
87
89
|
plugins: [],
|
|
88
90
|
resolvedPlugins: [],
|
|
89
91
|
};
|
|
90
|
-
const environmentOptions = getEnvironmentConfig();
|
|
92
|
+
const environmentOptions = getEnvironmentConfig(false);
|
|
91
93
|
let contentfulCliOptions = {};
|
|
92
94
|
try {
|
|
93
95
|
const contentfulConfig = await loadConfig('contentful');
|
|
94
|
-
if (!contentfulConfig.isEmpty) {
|
|
96
|
+
if (contentfulConfig && !contentfulConfig.isEmpty) {
|
|
95
97
|
const { managementToken, activeSpaceId, activeEnvironmentId, host } = contentfulConfig.config;
|
|
96
98
|
contentfulCliOptions = removeEmpty({
|
|
97
99
|
spaceId: activeSpaceId,
|
|
98
100
|
managementToken,
|
|
99
101
|
environmentId: activeEnvironmentId,
|
|
100
102
|
host,
|
|
101
|
-
});
|
|
103
|
+
}, false);
|
|
102
104
|
}
|
|
103
105
|
}
|
|
104
106
|
catch (error) {
|
|
@@ -116,7 +118,7 @@ export const getConfig = async (args) => {
|
|
|
116
118
|
args.rootDir = process.cwd();
|
|
117
119
|
try {
|
|
118
120
|
const configFile = await loadConfig('contentful-ssg');
|
|
119
|
-
if (!configFile.isEmpty) {
|
|
121
|
+
if (configFile && !configFile.isEmpty) {
|
|
120
122
|
configFileOptions = configFile.config;
|
|
121
123
|
args.rootDir = dirname(configFile.filepath);
|
|
122
124
|
if (configFileOptions.directory && !isAbsolute(configFileOptions.directory)) {
|
|
@@ -140,5 +142,10 @@ export const getConfig = async (args) => {
|
|
|
140
142
|
...result.resolvedPlugins,
|
|
141
143
|
...(await Promise.all((result.plugins || []).map(async (plugin) => resolvePlugin(plugin, result)))),
|
|
142
144
|
];
|
|
143
|
-
|
|
145
|
+
result.managedDirectories = [...result.managedDirectories, result.directory];
|
|
146
|
+
const hookedConfig = await reduceAsync(resolvedPlugins.filter((plugin) => typeof plugin.config === 'function'), async (prev, hooks) => {
|
|
147
|
+
const hook = hooks.config;
|
|
148
|
+
return hook(prev);
|
|
149
|
+
}, result);
|
|
150
|
+
return { ...hookedConfig, ...result, resolvedPlugins };
|
|
144
151
|
};
|
package/dist/lib/file-manager.js
CHANGED
|
@@ -2,6 +2,7 @@ import { dirname, resolve, relative, join } from 'path';
|
|
|
2
2
|
import ignore from 'ignore';
|
|
3
3
|
import { readFile, readdir, lstat } from 'fs/promises';
|
|
4
4
|
import { remove, outputFile } from 'fs-extra';
|
|
5
|
+
import { existsSync } from 'fs';
|
|
5
6
|
export class FileManager {
|
|
6
7
|
ignoreBase = process.cwd();
|
|
7
8
|
ignore;
|
|
@@ -25,7 +26,11 @@ export class FileManager {
|
|
|
25
26
|
const ignorePatterns = await readFile(gitignore);
|
|
26
27
|
this.ignore = ignore().add(ignorePatterns.toString('utf8'));
|
|
27
28
|
}
|
|
28
|
-
const
|
|
29
|
+
const directories = [
|
|
30
|
+
...new Set([...(this.config.managedDirectories || []), this.config.directory]),
|
|
31
|
+
];
|
|
32
|
+
const globPattern = directories.map((directory) => resolve(this.config.rootDir || '', directory, '**/*.*'));
|
|
33
|
+
const existing = await globby(globPattern);
|
|
29
34
|
this.files = new Set(existing.map((file) => resolve(file)));
|
|
30
35
|
}
|
|
31
36
|
async writeFile(file, data, options) {
|
|
@@ -51,7 +56,7 @@ export class FileManager {
|
|
|
51
56
|
await Promise.all(recursiveRemovalPromises);
|
|
52
57
|
fileNames = await readdir(directory);
|
|
53
58
|
}
|
|
54
|
-
if (fileNames.length === 0 &&
|
|
59
|
+
if (fileNames.length === 0 && ![this.config.directory, 'data'].includes(directory)) {
|
|
55
60
|
await remove(directory);
|
|
56
61
|
}
|
|
57
62
|
}
|
|
@@ -59,6 +64,9 @@ export class FileManager {
|
|
|
59
64
|
const promises = [...this.ignoredFiles].map(async (file) => this.deleteFile(file));
|
|
60
65
|
await Promise.allSettled(promises);
|
|
61
66
|
await this.removeEmptyDirectories(this.config.directory);
|
|
67
|
+
if (existsSync('data')) {
|
|
68
|
+
await this.removeEmptyDirectories('data');
|
|
69
|
+
}
|
|
62
70
|
return true;
|
|
63
71
|
}
|
|
64
72
|
}
|
package/dist/lib/object.d.ts
CHANGED
|
@@ -5,7 +5,8 @@ export declare const isObject: (something: any) => boolean;
|
|
|
5
5
|
export declare const getEntries: <T>(obj: T) => Entries<T>;
|
|
6
6
|
export declare const fromEntries: <T = [string, unknown][]>(entries: Entries<T>) => T;
|
|
7
7
|
export declare const omitKeys: <T, K extends keyof T>(obj: T, ...keys: K[]) => T;
|
|
8
|
-
export declare const
|
|
8
|
+
export declare const filterKeys: <T, K extends keyof T>(obj: T, ...keys: K[]) => T;
|
|
9
|
+
export declare const removeEmpty: <T>(iterable: T, strict?: boolean) => T;
|
|
9
10
|
export declare const snakeCaseKeys: <T>(iterable: T) => T;
|
|
10
11
|
export declare const groupBy: <T extends Record<string, unknown>>(array: T[], key: keyof T) => Record<string, T[]>;
|
|
11
12
|
export {};
|
package/dist/lib/object.js
CHANGED
|
@@ -8,14 +8,19 @@ export const omitKeys = (obj, ...keys) => {
|
|
|
8
8
|
const filtered = entries.filter(([key]) => !keys.includes(key));
|
|
9
9
|
return fromEntries(filtered);
|
|
10
10
|
};
|
|
11
|
-
export const
|
|
11
|
+
export const filterKeys = (obj, ...keys) => {
|
|
12
|
+
const entries = getEntries(obj);
|
|
13
|
+
const filtered = entries.filter(([key]) => keys.includes(key));
|
|
14
|
+
return fromEntries(filtered);
|
|
15
|
+
};
|
|
16
|
+
export const removeEmpty = (iterable, strict = true) => {
|
|
12
17
|
if (Array.isArray(iterable)) {
|
|
13
18
|
return iterable
|
|
14
|
-
.filter((v) => v !== null && v !== undefined)
|
|
19
|
+
.filter((v) => v !== null && v !== undefined && (strict || Boolean(v)))
|
|
15
20
|
.map((v) => (v === Object(v) ? removeEmpty(v) : v));
|
|
16
21
|
}
|
|
17
22
|
return fromEntries(getEntries(iterable)
|
|
18
|
-
.filter(([, v]) => v !== null && v !== undefined)
|
|
23
|
+
.filter(([, v]) => v !== null && v !== undefined && (strict || Boolean(v)))
|
|
19
24
|
.map(([k, v]) => [k, v === Object(v) ? removeEmpty(v) : v]));
|
|
20
25
|
};
|
|
21
26
|
export const snakeCaseKeys = (iterable) => {
|
package/dist/types.d.ts
CHANGED
|
@@ -30,10 +30,12 @@ export interface ContentfulRcConfig {
|
|
|
30
30
|
activeEnvironmentId: string;
|
|
31
31
|
host: string;
|
|
32
32
|
}
|
|
33
|
+
export declare type ConfigHook = (config: Config) => Config | Promise<Config>;
|
|
33
34
|
export declare type RuntimeHook = (runtimeContext: RuntimeContext) => Promise<Partial<RuntimeContext>> | Partial<RuntimeContext> | void;
|
|
34
35
|
export declare type TransformHook<T> = (transformContext: TransformContext, runtimeContext?: RuntimeContext, prev?: T) => Promise<T> | T;
|
|
35
36
|
export declare type ValidateHook = (transformContext: TransformContext, runtimeContext?: RuntimeContext) => Promise<boolean> | boolean;
|
|
36
37
|
export interface Hooks {
|
|
38
|
+
config?: ConfigHook;
|
|
37
39
|
before?: RuntimeHook;
|
|
38
40
|
after?: RuntimeHook;
|
|
39
41
|
transform?: TransformHook<KeyValueMap>;
|
|
@@ -46,6 +48,7 @@ export interface Hooks {
|
|
|
46
48
|
export declare type Config = Partial<ContentfulConfig> & Hooks & {
|
|
47
49
|
rootDir?: string;
|
|
48
50
|
directory: string;
|
|
51
|
+
managedDirectories?: string[];
|
|
49
52
|
verbose?: boolean;
|
|
50
53
|
plugins?: Array<[string, KeyValueMap] | PluginInfo | string>;
|
|
51
54
|
resolvedPlugins?: Hooks[];
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jungvonmatt/contentful-ssg",
|
|
3
|
-
"version": "1.0
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"description": "",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"type": "module",
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
".": "./dist/index.js",
|
|
10
10
|
"./lib/object": "./dist/lib/object.js",
|
|
11
11
|
"./lib/array": "./dist/lib/array.js",
|
|
12
|
+
"./lib/utils": "./dist/lib/utils.js",
|
|
12
13
|
"./lib/contentful": "./dist/lib/contentful.js",
|
|
13
14
|
"./lib/hook-manager": "./dist/lib/hook-manager.js",
|
|
14
15
|
"./lib/file-manager": "./dist/lib/file-manager.js",
|
|
@@ -23,6 +24,9 @@
|
|
|
23
24
|
".": [
|
|
24
25
|
"./dist/types.d.ts"
|
|
25
26
|
],
|
|
27
|
+
"lib/utils": [
|
|
28
|
+
"./dist/lib/utils.d.ts"
|
|
29
|
+
],
|
|
26
30
|
"lib/object": [
|
|
27
31
|
"./dist/lib/object.d.ts"
|
|
28
32
|
],
|
|
@@ -91,6 +95,7 @@
|
|
|
91
95
|
"dotenv": "^10.0.0",
|
|
92
96
|
"dotenv-expand": "^5.1.0",
|
|
93
97
|
"esbuild": "^0.13.13",
|
|
98
|
+
"find-up": "^6.2.0",
|
|
94
99
|
"fs-extra": "^10.0.0",
|
|
95
100
|
"globby": "^12.0.2",
|
|
96
101
|
"gray-matter": "^4.0.3",
|
|
@@ -138,5 +143,5 @@
|
|
|
138
143
|
"module": "es2020"
|
|
139
144
|
}
|
|
140
145
|
},
|
|
141
|
-
"gitHead": "
|
|
146
|
+
"gitHead": "95f58f6323beda2a6ba1b3cb6fecd07ff6d6cc7f"
|
|
142
147
|
}
|
package/src/cli.ts
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
import path from 'path';
|
|
5
5
|
import chalk from 'chalk';
|
|
6
6
|
import { existsSync } from 'fs';
|
|
7
|
+
import { readFile } from 'fs/promises';
|
|
7
8
|
import { outputFile } from 'fs-extra';
|
|
8
9
|
import prettier from 'prettier';
|
|
9
10
|
import { Command } from 'commander';
|
|
@@ -67,9 +68,34 @@ program
|
|
|
67
68
|
verified.directory = path.relative(process.cwd(), verified.directory);
|
|
68
69
|
}
|
|
69
70
|
|
|
70
|
-
const environmentKeys: Array<keyof ContentfulConfig> = (
|
|
71
|
-
|
|
72
|
-
)
|
|
71
|
+
const environmentKeys: Array<keyof ContentfulConfig> = Object.keys(
|
|
72
|
+
environmentConfig
|
|
73
|
+
) as Array<keyof ContentfulConfig>;
|
|
74
|
+
|
|
75
|
+
// Update .env file
|
|
76
|
+
if (environmentConfig && existsSync('.env')) {
|
|
77
|
+
const envSource = await readFile('.env', 'utf8');
|
|
78
|
+
const nextEnvSource = envSource
|
|
79
|
+
.replace(/(CONTENTFUL_SPACE_ID\s*=\s*['"]?)[^'"]*(['"]?)/, `$1${verified.spaceId}$2`)
|
|
80
|
+
.replace(
|
|
81
|
+
/(CONTENTFUL_ENVIRONMENT_ID\s*=\s*['"]?)[^'"]*(['"]?)/,
|
|
82
|
+
`$1${verified.environmentId}$2`
|
|
83
|
+
)
|
|
84
|
+
.replace(
|
|
85
|
+
/(CONTENTFUL_MANAGEMENT_TOKEN\s*=\s*['"]?)[^'"]*(['"]?)/,
|
|
86
|
+
`$1${verified.managementToken}$2`
|
|
87
|
+
)
|
|
88
|
+
.replace(
|
|
89
|
+
/(CONTENTFUL_PREVIEW_TOKEN\s*=\s*['"]?)[^'"]*(['"]?)/,
|
|
90
|
+
`$1${verified.previewAccessToken}$2`
|
|
91
|
+
)
|
|
92
|
+
.replace(
|
|
93
|
+
/(CONTENTFUL_DELIVERY_TOKEN\s*=\s*['"]?)[^'"]*(['"]?)/,
|
|
94
|
+
`$1${verified.accessToken}$2`
|
|
95
|
+
);
|
|
96
|
+
|
|
97
|
+
await outputFile('.env', nextEnvSource);
|
|
98
|
+
}
|
|
73
99
|
|
|
74
100
|
const cleanedConfig = omitKeys(
|
|
75
101
|
verified,
|
package/src/lib/config.ts
CHANGED
|
@@ -14,6 +14,7 @@ import type {
|
|
|
14
14
|
PluginInfo,
|
|
15
15
|
PluginModule,
|
|
16
16
|
} from '../types.js';
|
|
17
|
+
import { reduceAsync } from './array.js';
|
|
17
18
|
import { createRequire } from './create-require.js';
|
|
18
19
|
import { isObject, removeEmpty } from './object.js';
|
|
19
20
|
|
|
@@ -105,14 +106,17 @@ const loadConfig = async (moduleName: string): Promise<CosmiconfigResult> => {
|
|
|
105
106
|
return explorer.search();
|
|
106
107
|
};
|
|
107
108
|
|
|
108
|
-
export const getEnvironmentConfig = (): ContentfulConfig =>
|
|
109
|
-
removeEmpty(
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
109
|
+
export const getEnvironmentConfig = (strict = true): ContentfulConfig =>
|
|
110
|
+
removeEmpty(
|
|
111
|
+
{
|
|
112
|
+
spaceId: process.env.CONTENTFUL_SPACE_ID!,
|
|
113
|
+
environmentId: process.env.CONTENTFUL_ENVIRONMENT_ID!,
|
|
114
|
+
managementToken: process.env.CONTENTFUL_MANAGEMENT_TOKEN!,
|
|
115
|
+
previewAccessToken: process.env.CONTENTFUL_PREVIEW_TOKEN!,
|
|
116
|
+
accessToken: process.env.CONTENTFUL_DELIVERY_TOKEN!,
|
|
117
|
+
},
|
|
118
|
+
strict
|
|
119
|
+
);
|
|
116
120
|
|
|
117
121
|
/**
|
|
118
122
|
* Get configuration
|
|
@@ -123,26 +127,30 @@ export const getConfig = async (args?: Partial<Config>): Promise<Config> => {
|
|
|
123
127
|
environmentId: 'master',
|
|
124
128
|
host: 'api.contentful.com',
|
|
125
129
|
directory: resolve(process.cwd(), 'content'),
|
|
130
|
+
managedDirectories: [],
|
|
126
131
|
plugins: [],
|
|
127
132
|
resolvedPlugins: [],
|
|
128
133
|
};
|
|
129
134
|
|
|
130
|
-
const environmentOptions = getEnvironmentConfig();
|
|
135
|
+
const environmentOptions = getEnvironmentConfig(false);
|
|
131
136
|
let contentfulCliOptions: Partial<ContentfulConfig> = {};
|
|
132
137
|
|
|
133
138
|
try {
|
|
134
139
|
// Get configuration from contentful rc file (created by the contentful cli command)
|
|
135
140
|
const contentfulConfig = await loadConfig('contentful');
|
|
136
|
-
if (!contentfulConfig.isEmpty) {
|
|
141
|
+
if (contentfulConfig && !contentfulConfig.isEmpty) {
|
|
137
142
|
const { managementToken, activeSpaceId, activeEnvironmentId, host } =
|
|
138
143
|
contentfulConfig.config as ContentfulRcConfig;
|
|
139
144
|
|
|
140
|
-
contentfulCliOptions = removeEmpty(
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
145
|
+
contentfulCliOptions = removeEmpty(
|
|
146
|
+
{
|
|
147
|
+
spaceId: activeSpaceId,
|
|
148
|
+
managementToken,
|
|
149
|
+
environmentId: activeEnvironmentId,
|
|
150
|
+
host,
|
|
151
|
+
},
|
|
152
|
+
false
|
|
153
|
+
);
|
|
146
154
|
}
|
|
147
155
|
} catch (error: unknown) {
|
|
148
156
|
if (typeof error === 'string') {
|
|
@@ -159,7 +167,7 @@ export const getConfig = async (args?: Partial<Config>): Promise<Config> => {
|
|
|
159
167
|
try {
|
|
160
168
|
// Get configuration from contentful-ssg rc file
|
|
161
169
|
const configFile = await loadConfig('contentful-ssg');
|
|
162
|
-
if (!configFile.isEmpty) {
|
|
170
|
+
if (configFile && !configFile.isEmpty) {
|
|
163
171
|
configFileOptions = configFile.config as Partial<Config>;
|
|
164
172
|
args.rootDir = dirname(configFile.filepath);
|
|
165
173
|
if (configFileOptions.directory && !isAbsolute(configFileOptions.directory)) {
|
|
@@ -191,5 +199,17 @@ export const getConfig = async (args?: Partial<Config>): Promise<Config> => {
|
|
|
191
199
|
(result.plugins || []).map(async (plugin) => resolvePlugin(plugin, result))
|
|
192
200
|
)),
|
|
193
201
|
];
|
|
194
|
-
|
|
202
|
+
|
|
203
|
+
result.managedDirectories = [...result.managedDirectories, result.directory];
|
|
204
|
+
|
|
205
|
+
const hookedConfig = await reduceAsync(
|
|
206
|
+
resolvedPlugins.filter((plugin) => typeof plugin.config === 'function'),
|
|
207
|
+
async (prev: Config, hooks) => {
|
|
208
|
+
const hook = hooks.config;
|
|
209
|
+
return hook(prev);
|
|
210
|
+
},
|
|
211
|
+
result
|
|
212
|
+
);
|
|
213
|
+
|
|
214
|
+
return { ...hookedConfig, ...result, resolvedPlugins };
|
|
195
215
|
};
|
package/src/lib/file-manager.ts
CHANGED
|
@@ -4,6 +4,7 @@ import { dirname, resolve, relative, join } from 'path';
|
|
|
4
4
|
import ignore from 'ignore';
|
|
5
5
|
import { readFile, readdir, lstat } from 'fs/promises';
|
|
6
6
|
import { remove, outputFile } from 'fs-extra';
|
|
7
|
+
import { existsSync } from 'fs';
|
|
7
8
|
|
|
8
9
|
export class FileManager {
|
|
9
10
|
ignoreBase: string = process.cwd();
|
|
@@ -38,8 +39,15 @@ export class FileManager {
|
|
|
38
39
|
this.ignore = ignore().add(ignorePatterns.toString('utf8'));
|
|
39
40
|
}
|
|
40
41
|
|
|
42
|
+
const directories = [
|
|
43
|
+
...new Set([...(this.config.managedDirectories || []), this.config.directory]),
|
|
44
|
+
];
|
|
45
|
+
|
|
46
|
+
const globPattern = directories.map((directory) =>
|
|
47
|
+
resolve(this.config.rootDir || '', directory, '**/*.*')
|
|
48
|
+
);
|
|
41
49
|
// Create set of existing files
|
|
42
|
-
const existing = await globby(
|
|
50
|
+
const existing = await globby(globPattern);
|
|
43
51
|
|
|
44
52
|
this.files = new Set(existing.map((file) => resolve(file)));
|
|
45
53
|
}
|
|
@@ -91,7 +99,7 @@ export class FileManager {
|
|
|
91
99
|
fileNames = await readdir(directory);
|
|
92
100
|
}
|
|
93
101
|
|
|
94
|
-
if (fileNames.length === 0 &&
|
|
102
|
+
if (fileNames.length === 0 && ![this.config.directory, 'data'].includes(directory)) {
|
|
95
103
|
await remove(directory);
|
|
96
104
|
}
|
|
97
105
|
}
|
|
@@ -101,6 +109,10 @@ export class FileManager {
|
|
|
101
109
|
|
|
102
110
|
await Promise.allSettled(promises);
|
|
103
111
|
await this.removeEmptyDirectories(this.config.directory);
|
|
112
|
+
if (existsSync('data')) {
|
|
113
|
+
await this.removeEmptyDirectories('data');
|
|
114
|
+
}
|
|
115
|
+
|
|
104
116
|
return true;
|
|
105
117
|
}
|
|
106
118
|
}
|
package/src/lib/object.test.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/* eslint-disable @typescript-eslint/naming-convention */
|
|
2
|
-
import { isObject, omitKeys, removeEmpty, snakeCaseKeys, groupBy } from './object';
|
|
2
|
+
import { isObject, omitKeys, filterKeys, removeEmpty, snakeCaseKeys, groupBy } from './object';
|
|
3
3
|
|
|
4
4
|
test('isObject', async () => {
|
|
5
5
|
const arr = [];
|
|
@@ -17,6 +17,11 @@ test('omitKeys', () => {
|
|
|
17
17
|
expect(value).toEqual({ b: 2 });
|
|
18
18
|
});
|
|
19
19
|
|
|
20
|
+
test('filterKeys', () => {
|
|
21
|
+
const value = filterKeys({ a: 1, b: 2, c: 3 }, 'a', 'c');
|
|
22
|
+
expect(value).toEqual({ a: 1, c: 3 });
|
|
23
|
+
});
|
|
24
|
+
|
|
20
25
|
test('removeEmpty', () => {
|
|
21
26
|
const value = removeEmpty({
|
|
22
27
|
a: { c: undefined },
|
|
@@ -26,6 +31,16 @@ test('removeEmpty', () => {
|
|
|
26
31
|
expect(value).toEqual({ a: {}, c: [1, { x: 1, z: [7] }, 3, 5] });
|
|
27
32
|
});
|
|
28
33
|
|
|
34
|
+
|
|
35
|
+
test('removeEmpty non-strict', () => {
|
|
36
|
+
const value = removeEmpty({
|
|
37
|
+
a: "",
|
|
38
|
+
b: 0,
|
|
39
|
+
c: 1,
|
|
40
|
+
}, false);
|
|
41
|
+
expect(value).toEqual({ c: 1 });
|
|
42
|
+
});
|
|
43
|
+
|
|
29
44
|
test('groupBy', () => {
|
|
30
45
|
const value = groupBy(
|
|
31
46
|
[
|
package/src/lib/object.ts
CHANGED
|
@@ -30,16 +30,27 @@ export const omitKeys = <T, K extends keyof T>(obj: T, ...keys: K[]): T => {
|
|
|
30
30
|
return fromEntries(filtered);
|
|
31
31
|
};
|
|
32
32
|
|
|
33
|
+
/**
|
|
34
|
+
* Filter values by key from object
|
|
35
|
+
* @param {*} obj
|
|
36
|
+
* @param {*} keys
|
|
37
|
+
*/
|
|
38
|
+
export const filterKeys = <T, K extends keyof T>(obj: T, ...keys: K[]): T => {
|
|
39
|
+
const entries: Entries<T> = getEntries(obj);
|
|
40
|
+
const filtered = entries.filter(([key]) => keys.includes(key as K));
|
|
41
|
+
return fromEntries(filtered);
|
|
42
|
+
};
|
|
43
|
+
|
|
33
44
|
/**
|
|
34
45
|
* Recursive remove empty items (null,undefined) from object
|
|
35
46
|
* @param iterable Source object
|
|
36
47
|
* @returns Cleaned object
|
|
37
48
|
*/
|
|
38
|
-
export const removeEmpty = <T>(iterable: T): T => {
|
|
49
|
+
export const removeEmpty = <T>(iterable: T, strict = true): T => {
|
|
39
50
|
if (Array.isArray(iterable)) {
|
|
40
51
|
return (
|
|
41
52
|
iterable
|
|
42
|
-
.filter((v) => v !== null && v !== undefined)
|
|
53
|
+
.filter((v) => v !== null && v !== undefined && (strict || Boolean(v)))
|
|
43
54
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
|
|
44
55
|
.map((v) => (v === Object(v) ? removeEmpty(v) : v)) as unknown as T
|
|
45
56
|
);
|
|
@@ -47,7 +58,7 @@ export const removeEmpty = <T>(iterable: T): T => {
|
|
|
47
58
|
|
|
48
59
|
return fromEntries(
|
|
49
60
|
getEntries(iterable)
|
|
50
|
-
.filter(([, v]) => v !== null && v !== undefined)
|
|
61
|
+
.filter(([, v]) => v !== null && v !== undefined && (strict || Boolean(v)))
|
|
51
62
|
.map(([k, v]) => [k, v === Object(v) ? removeEmpty(v) : v])
|
|
52
63
|
);
|
|
53
64
|
};
|
package/src/types.ts
CHANGED
|
@@ -52,6 +52,8 @@ export interface ContentfulRcConfig {
|
|
|
52
52
|
host: string;
|
|
53
53
|
}
|
|
54
54
|
|
|
55
|
+
export type ConfigHook = (config: Config) => Config | Promise<Config>;
|
|
56
|
+
|
|
55
57
|
export type RuntimeHook = (
|
|
56
58
|
runtimeContext: RuntimeContext
|
|
57
59
|
) => Promise<Partial<RuntimeContext>> | Partial<RuntimeContext> | void;
|
|
@@ -66,6 +68,7 @@ export type ValidateHook = (
|
|
|
66
68
|
) => Promise<boolean> | boolean;
|
|
67
69
|
|
|
68
70
|
export interface Hooks {
|
|
71
|
+
config?: ConfigHook;
|
|
69
72
|
before?: RuntimeHook;
|
|
70
73
|
after?: RuntimeHook;
|
|
71
74
|
transform?: TransformHook<KeyValueMap>;
|
|
@@ -80,6 +83,7 @@ export type Config = Partial<ContentfulConfig> &
|
|
|
80
83
|
Hooks & {
|
|
81
84
|
rootDir?: string;
|
|
82
85
|
directory: string;
|
|
86
|
+
managedDirectories?: string[];
|
|
83
87
|
verbose?: boolean;
|
|
84
88
|
plugins?: Array<[string, KeyValueMap] | PluginInfo | string>;
|
|
85
89
|
resolvedPlugins?: Hooks[];
|