@ahmedrowaihi/pdf-forge-cli 1.0.0-canary.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/.turbo/turbo-build.log +14 -0
- package/CHANGELOG.md +7 -0
- package/LICENSE.md +8 -0
- package/dist/index.js +1058 -0
- package/package.json +60 -0
- package/src/commands/build.ts +249 -0
- package/src/commands/dev.ts +27 -0
- package/src/commands/export.ts +204 -0
- package/src/commands/start.ts +35 -0
- package/src/index.ts +67 -0
- package/src/utils/conf.ts +10 -0
- package/src/utils/esbuild/escape-string-for-regex.ts +3 -0
- package/src/utils/esbuild/renderring-utilities-exporter.ts +62 -0
- package/src/utils/get-preview-server-location.ts +108 -0
- package/src/utils/get-templates-directory-metadata.ts +139 -0
- package/src/utils/index.ts +3 -0
- package/src/utils/packageJson.ts +4 -0
- package/src/utils/preview/get-env-variables-for-preview-app.ts +17 -0
- package/src/utils/preview/hot-reloading/create-dependency-graph.ts +345 -0
- package/src/utils/preview/hot-reloading/get-imported-modules.ts +49 -0
- package/src/utils/preview/hot-reloading/resolve-path-aliases.ts +32 -0
- package/src/utils/preview/hot-reloading/setup-hot-reloading.ts +125 -0
- package/src/utils/preview/serve-static-file.ts +134 -0
- package/src/utils/preview/start-dev-server.ts +242 -0
- package/src/utils/register-spinner-autostopping.ts +29 -0
- package/src/utils/style-text.ts +11 -0
- package/src/utils/tree.ts +76 -0
- package/tsconfig.json +38 -0
- package/tsdown.config.ts +9 -0
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { promises as fs } from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import type { Loader, PluginBuild, ResolveOptions } from 'esbuild';
|
|
4
|
+
import { escapeStringForRegex } from './escape-string-for-regex.js';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Made to export the `render` function out of the user's PDF template
|
|
8
|
+
* so that React version mismatches don't happen.
|
|
9
|
+
*
|
|
10
|
+
* This also exports the `createElement` from the user's React version as well
|
|
11
|
+
* to avoid mismatches.
|
|
12
|
+
*
|
|
13
|
+
* This avoids multiple versions of React being involved, i.e., the version
|
|
14
|
+
* in the CLI vs. the version the user has on their templates.
|
|
15
|
+
*/
|
|
16
|
+
export const renderingUtilitiesExporter = (pdfTemplates: string[]) => ({
|
|
17
|
+
name: 'rendering-utilities-exporter',
|
|
18
|
+
setup: (b: PluginBuild) => {
|
|
19
|
+
b.onLoad(
|
|
20
|
+
{
|
|
21
|
+
filter: new RegExp(
|
|
22
|
+
pdfTemplates
|
|
23
|
+
.map((templatePath) => escapeStringForRegex(templatePath))
|
|
24
|
+
.join('|'),
|
|
25
|
+
),
|
|
26
|
+
},
|
|
27
|
+
async ({ path: pathToFile }) => {
|
|
28
|
+
return {
|
|
29
|
+
contents: `${await fs.readFile(pathToFile, 'utf8')};
|
|
30
|
+
export { render } from 'react-pdf-module-that-will-export-render'
|
|
31
|
+
export { createElement as reactPDFCreateReactElement } from 'react';
|
|
32
|
+
`,
|
|
33
|
+
loader: path.extname(pathToFile).slice(1) as Loader,
|
|
34
|
+
};
|
|
35
|
+
},
|
|
36
|
+
);
|
|
37
|
+
|
|
38
|
+
b.onResolve(
|
|
39
|
+
{ filter: /^react-pdf-module-that-will-export-render$/ },
|
|
40
|
+
async (args) => {
|
|
41
|
+
const options: ResolveOptions = {
|
|
42
|
+
kind: 'import-statement',
|
|
43
|
+
importer: args.importer,
|
|
44
|
+
resolveDir: args.resolveDir,
|
|
45
|
+
namespace: args.namespace,
|
|
46
|
+
};
|
|
47
|
+
let result = await b.resolve('@ahmedrowaihi/pdf-forge-core', options);
|
|
48
|
+
if (result.errors.length === 0) {
|
|
49
|
+
return result;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// If @ahmedrowaihi/pdf-forge-core does not exist, resolve to @ahmedrowaihi/pdf-forge-components
|
|
53
|
+
result = await b.resolve('@ahmedrowaihi/pdf-forge-components', options);
|
|
54
|
+
if (result.errors.length > 0 && result.errors[0]) {
|
|
55
|
+
result.errors[0].text =
|
|
56
|
+
"Failed trying to import `render` from either `@ahmedrowaihi/pdf-forge-core` or `@ahmedrowaihi/pdf-forge-components` to be able to render your PDF template.\n Maybe you don't have either of them installed?";
|
|
57
|
+
}
|
|
58
|
+
return result;
|
|
59
|
+
},
|
|
60
|
+
);
|
|
61
|
+
},
|
|
62
|
+
});
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import url from 'node:url';
|
|
4
|
+
import { createJiti } from 'jiti';
|
|
5
|
+
import { addDevDependency } from 'nypm';
|
|
6
|
+
import prompts from 'prompts';
|
|
7
|
+
import { packageJson } from './packageJson.js';
|
|
8
|
+
|
|
9
|
+
const ensurePreviewServerInstalled = async (
|
|
10
|
+
message: string,
|
|
11
|
+
): Promise<never> => {
|
|
12
|
+
const response = await prompts({
|
|
13
|
+
type: 'confirm',
|
|
14
|
+
name: 'installPreviewServer',
|
|
15
|
+
message,
|
|
16
|
+
initial: true,
|
|
17
|
+
});
|
|
18
|
+
if (response.installPreviewServer) {
|
|
19
|
+
console.log('Installing "@ahmedrowaihi/pdf-forge-preview"');
|
|
20
|
+
await addDevDependency(
|
|
21
|
+
`@ahmedrowaihi/pdf-forge-preview@${packageJson.version}`,
|
|
22
|
+
);
|
|
23
|
+
process.exit(0);
|
|
24
|
+
} else {
|
|
25
|
+
process.exit(0);
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
const findWorkspacePreviewServer = (): string | null => {
|
|
30
|
+
const cwd = process.cwd();
|
|
31
|
+
|
|
32
|
+
let workspaceRoot: string | null = null;
|
|
33
|
+
let currentPath = cwd;
|
|
34
|
+
while (currentPath !== path.dirname(currentPath)) {
|
|
35
|
+
const pnpmWorkspace = path.join(currentPath, 'pnpm-workspace.yaml');
|
|
36
|
+
if (fs.existsSync(pnpmWorkspace)) {
|
|
37
|
+
workspaceRoot = currentPath;
|
|
38
|
+
break;
|
|
39
|
+
}
|
|
40
|
+
currentPath = path.dirname(currentPath);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
if (workspaceRoot) {
|
|
44
|
+
const previewServerPath = path.resolve(
|
|
45
|
+
workspaceRoot,
|
|
46
|
+
'packages/preview-server',
|
|
47
|
+
);
|
|
48
|
+
const packageJsonPath = path.join(previewServerPath, 'package.json');
|
|
49
|
+
if (fs.existsSync(packageJsonPath)) {
|
|
50
|
+
try {
|
|
51
|
+
const pkg = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8')) as {
|
|
52
|
+
name: string;
|
|
53
|
+
};
|
|
54
|
+
if (pkg.name === '@ahmedrowaihi/pdf-forge-preview') {
|
|
55
|
+
return previewServerPath;
|
|
56
|
+
}
|
|
57
|
+
} catch {
|
|
58
|
+
// Invalid package.json, continue
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
return null;
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
export const getPreviewServerLocation = async () => {
|
|
66
|
+
const usersProject = createJiti(process.cwd());
|
|
67
|
+
let previewServerLocation!: string;
|
|
68
|
+
|
|
69
|
+
// First try to find it in workspace
|
|
70
|
+
const workspacePath = findWorkspacePreviewServer();
|
|
71
|
+
if (workspacePath) {
|
|
72
|
+
previewServerLocation = workspacePath;
|
|
73
|
+
} else {
|
|
74
|
+
// Try to resolve from node_modules
|
|
75
|
+
try {
|
|
76
|
+
previewServerLocation = path.dirname(
|
|
77
|
+
url.fileURLToPath(
|
|
78
|
+
usersProject.esmResolve('@ahmedrowaihi/pdf-forge-preview'),
|
|
79
|
+
),
|
|
80
|
+
);
|
|
81
|
+
} catch {
|
|
82
|
+
await ensurePreviewServerInstalled(
|
|
83
|
+
'To run the preview server, the package "@ahmedrowaihi/pdf-forge-preview" must be installed. Would you like to install it?',
|
|
84
|
+
);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// If we found it in workspace, skip version check (workspace packages are always in sync)
|
|
89
|
+
if (!workspacePath) {
|
|
90
|
+
// Verify version if we can import it (only for non-workspace installations)
|
|
91
|
+
try {
|
|
92
|
+
const { version } = await usersProject.import<{
|
|
93
|
+
version: string;
|
|
94
|
+
}>('@ahmedrowaihi/pdf-forge-preview');
|
|
95
|
+
if (version !== packageJson.version) {
|
|
96
|
+
await ensurePreviewServerInstalled(
|
|
97
|
+
`To run the preview server, the version of "@ahmedrowaihi/pdf-forge-preview" must match the version of "@ahmedrowaihi/pdf-forge-cli" (${packageJson.version}). Would you like to install it?`,
|
|
98
|
+
);
|
|
99
|
+
}
|
|
100
|
+
} catch {
|
|
101
|
+
await ensurePreviewServerInstalled(
|
|
102
|
+
'To run the preview server, the package "@ahmedrowaihi/pdf-forge-preview" must be installed. Would you like to install it?',
|
|
103
|
+
);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
return previewServerLocation;
|
|
108
|
+
};
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
|
|
4
|
+
const isFileATemplate = async (fullPath: string): Promise<boolean> => {
|
|
5
|
+
let fileHandle: fs.promises.FileHandle;
|
|
6
|
+
try {
|
|
7
|
+
fileHandle = await fs.promises.open(fullPath, 'r');
|
|
8
|
+
} catch (exception) {
|
|
9
|
+
console.warn(exception);
|
|
10
|
+
return false;
|
|
11
|
+
}
|
|
12
|
+
const stat = await fileHandle.stat();
|
|
13
|
+
|
|
14
|
+
if (stat.isDirectory()) {
|
|
15
|
+
await fileHandle.close();
|
|
16
|
+
return false;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const { ext } = path.parse(fullPath);
|
|
20
|
+
|
|
21
|
+
if (!['.js', '.tsx', '.jsx'].includes(ext)) {
|
|
22
|
+
await fileHandle.close();
|
|
23
|
+
return false;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// check with a heuristic to see if the file has at least
|
|
27
|
+
// a default export (ES6) or module.exports (CommonJS) or named exports (MDX)
|
|
28
|
+
const fileContents = await fileHandle.readFile('utf8');
|
|
29
|
+
|
|
30
|
+
await fileHandle.close();
|
|
31
|
+
|
|
32
|
+
// Check for ES6 export default syntax
|
|
33
|
+
const hasES6DefaultExport = /\bexport\s+default\b/gm.test(fileContents);
|
|
34
|
+
|
|
35
|
+
// Check for CommonJS module.exports syntax
|
|
36
|
+
const hasCommonJSExport = /\bmodule\.exports\s*=/gm.test(fileContents);
|
|
37
|
+
|
|
38
|
+
// Check for named exports (used in MDX files) and ensure at least one is marked as default
|
|
39
|
+
const hasNamedExport = /\bexport\s+\{[^}]*\bdefault\b[^}]*\}/gm.test(
|
|
40
|
+
fileContents,
|
|
41
|
+
);
|
|
42
|
+
|
|
43
|
+
return hasES6DefaultExport || hasCommonJSExport || hasNamedExport;
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
export interface TemplatesDirectory {
|
|
47
|
+
absolutePath: string;
|
|
48
|
+
relativePath: string;
|
|
49
|
+
directoryName: string;
|
|
50
|
+
templateFilenames: string[];
|
|
51
|
+
subDirectories: TemplatesDirectory[];
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const mergeDirectoriesWithSubDirectories = (
|
|
55
|
+
templatesDirectoryMetadata: TemplatesDirectory,
|
|
56
|
+
): TemplatesDirectory => {
|
|
57
|
+
let currentResultingMergedDirectory: TemplatesDirectory =
|
|
58
|
+
templatesDirectoryMetadata;
|
|
59
|
+
|
|
60
|
+
while (
|
|
61
|
+
currentResultingMergedDirectory.templateFilenames.length === 0 &&
|
|
62
|
+
currentResultingMergedDirectory.subDirectories.length === 1
|
|
63
|
+
) {
|
|
64
|
+
const onlySubDirectory = currentResultingMergedDirectory.subDirectories[0]!;
|
|
65
|
+
currentResultingMergedDirectory = {
|
|
66
|
+
...onlySubDirectory,
|
|
67
|
+
directoryName: path.join(
|
|
68
|
+
currentResultingMergedDirectory.directoryName,
|
|
69
|
+
onlySubDirectory.directoryName,
|
|
70
|
+
),
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return currentResultingMergedDirectory;
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
export const getTemplatesDirectoryMetadata = async (
|
|
78
|
+
absolutePathToTemplatesDirectory: string,
|
|
79
|
+
keepFileExtensions = false,
|
|
80
|
+
isSubDirectory = false,
|
|
81
|
+
baseDirectoryPath = absolutePathToTemplatesDirectory,
|
|
82
|
+
): Promise<TemplatesDirectory | undefined> => {
|
|
83
|
+
if (!fs.existsSync(absolutePathToTemplatesDirectory)) return;
|
|
84
|
+
|
|
85
|
+
const dirents = await fs.promises.readdir(absolutePathToTemplatesDirectory, {
|
|
86
|
+
withFileTypes: true,
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
const isTemplatePredicates = await Promise.all(
|
|
90
|
+
dirents.map((dirent) =>
|
|
91
|
+
isFileATemplate(path.join(absolutePathToTemplatesDirectory, dirent.name)),
|
|
92
|
+
),
|
|
93
|
+
);
|
|
94
|
+
const templateFilenames = dirents
|
|
95
|
+
.filter((_, i) => isTemplatePredicates[i])
|
|
96
|
+
.map((dirent) =>
|
|
97
|
+
keepFileExtensions
|
|
98
|
+
? dirent.name
|
|
99
|
+
: dirent.name.replace(path.extname(dirent.name), ''),
|
|
100
|
+
);
|
|
101
|
+
|
|
102
|
+
const subDirectories = await Promise.all(
|
|
103
|
+
dirents
|
|
104
|
+
.filter(
|
|
105
|
+
(dirent) =>
|
|
106
|
+
dirent.isDirectory() &&
|
|
107
|
+
!dirent.name.startsWith('_') &&
|
|
108
|
+
dirent.name !== 'static',
|
|
109
|
+
)
|
|
110
|
+
.map((dirent) => {
|
|
111
|
+
const direntAbsolutePath = path.join(
|
|
112
|
+
absolutePathToTemplatesDirectory,
|
|
113
|
+
dirent.name,
|
|
114
|
+
);
|
|
115
|
+
|
|
116
|
+
return getTemplatesDirectoryMetadata(
|
|
117
|
+
direntAbsolutePath,
|
|
118
|
+
keepFileExtensions,
|
|
119
|
+
true,
|
|
120
|
+
baseDirectoryPath,
|
|
121
|
+
) as Promise<TemplatesDirectory>;
|
|
122
|
+
}),
|
|
123
|
+
);
|
|
124
|
+
|
|
125
|
+
const templatesMetadata = {
|
|
126
|
+
absolutePath: absolutePathToTemplatesDirectory,
|
|
127
|
+
relativePath: path.relative(
|
|
128
|
+
baseDirectoryPath,
|
|
129
|
+
absolutePathToTemplatesDirectory,
|
|
130
|
+
),
|
|
131
|
+
directoryName: absolutePathToTemplatesDirectory.split(path.sep).pop()!,
|
|
132
|
+
templateFilenames,
|
|
133
|
+
subDirectories,
|
|
134
|
+
} satisfies TemplatesDirectory;
|
|
135
|
+
|
|
136
|
+
return isSubDirectory
|
|
137
|
+
? mergeDirectoriesWithSubDirectories(templatesMetadata)
|
|
138
|
+
: templatesMetadata;
|
|
139
|
+
};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
|
|
3
|
+
export const getEnvVariablesForPreviewApp = (
|
|
4
|
+
relativePathToTemplatesDirectory: string,
|
|
5
|
+
previewServerLocation: string,
|
|
6
|
+
cwd: string,
|
|
7
|
+
) => {
|
|
8
|
+
return {
|
|
9
|
+
TEMPLATES_DIR_RELATIVE_PATH: relativePathToTemplatesDirectory,
|
|
10
|
+
TEMPLATES_DIR_ABSOLUTE_PATH: path.resolve(
|
|
11
|
+
cwd,
|
|
12
|
+
relativePathToTemplatesDirectory,
|
|
13
|
+
),
|
|
14
|
+
PREVIEW_SERVER_LOCATION: previewServerLocation,
|
|
15
|
+
USER_PROJECT_LOCATION: cwd,
|
|
16
|
+
} as const;
|
|
17
|
+
};
|
|
@@ -0,0 +1,345 @@
|
|
|
1
|
+
import { existsSync, promises as fs, statSync } from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import type { EventName } from 'chokidar/handler.js';
|
|
4
|
+
import { getImportedModules } from './get-imported-modules.js';
|
|
5
|
+
import { resolvePathAliases } from './resolve-path-aliases.js';
|
|
6
|
+
|
|
7
|
+
interface Module {
|
|
8
|
+
path: string;
|
|
9
|
+
|
|
10
|
+
dependencyPaths: string[];
|
|
11
|
+
dependentPaths: string[];
|
|
12
|
+
|
|
13
|
+
moduleDependencies: string[];
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export type DependencyGraph = Record</* path to module */ string, Module>;
|
|
17
|
+
|
|
18
|
+
const readAllFilesInsideDirectory = async (directory: string) => {
|
|
19
|
+
let allFilePaths: string[] = [];
|
|
20
|
+
|
|
21
|
+
const topLevelDirents = await fs.readdir(directory, { withFileTypes: true });
|
|
22
|
+
|
|
23
|
+
for (const dirent of topLevelDirents) {
|
|
24
|
+
const pathToDirent = path.join(directory, dirent.name);
|
|
25
|
+
if (dirent.isDirectory()) {
|
|
26
|
+
allFilePaths = allFilePaths.concat(
|
|
27
|
+
await readAllFilesInsideDirectory(pathToDirent),
|
|
28
|
+
);
|
|
29
|
+
} else {
|
|
30
|
+
allFilePaths.push(pathToDirent);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
return allFilePaths;
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
const javascriptExtensions = ['.js', '.ts', '.jsx', '.tsx', '.mjs', '.cjs'];
|
|
38
|
+
|
|
39
|
+
const isJavascriptModule = (filePath: string) => {
|
|
40
|
+
const extensionName = path.extname(filePath);
|
|
41
|
+
|
|
42
|
+
return javascriptExtensions.includes(extensionName);
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
const checkFileExtensionsUntilItExists = (
|
|
46
|
+
pathWithoutExtension: string,
|
|
47
|
+
): string | undefined => {
|
|
48
|
+
if (existsSync(`${pathWithoutExtension}.ts`)) {
|
|
49
|
+
return `${pathWithoutExtension}.ts`;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if (existsSync(`${pathWithoutExtension}.tsx`)) {
|
|
53
|
+
return `${pathWithoutExtension}.tsx`;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if (existsSync(`${pathWithoutExtension}.js`)) {
|
|
57
|
+
return `${pathWithoutExtension}.js`;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
if (existsSync(`${pathWithoutExtension}.jsx`)) {
|
|
61
|
+
return `${pathWithoutExtension}.jsx`;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
if (existsSync(`${pathWithoutExtension}.mjs`)) {
|
|
65
|
+
return `${pathWithoutExtension}.mjs`;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
if (existsSync(`${pathWithoutExtension}.cjs`)) {
|
|
69
|
+
return `${pathWithoutExtension}.cjs`;
|
|
70
|
+
}
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Creates a stateful dependency graph that is structured in a way that you can get
|
|
75
|
+
* the dependents of a module from its path.
|
|
76
|
+
*
|
|
77
|
+
* Stateful in the sense that it provides a `getter` and an "`updater`". The updater
|
|
78
|
+
* will receive changes to the files, that can be perceived through some file watching mechanism,
|
|
79
|
+
* so that it doesn't need to recompute the entire dependency graph but only the parts changed.
|
|
80
|
+
*/
|
|
81
|
+
export const createDependencyGraph = async (directory: string) => {
|
|
82
|
+
const filePaths = await readAllFilesInsideDirectory(directory);
|
|
83
|
+
const modulePaths = filePaths.filter(isJavascriptModule);
|
|
84
|
+
const graph: DependencyGraph = Object.fromEntries(
|
|
85
|
+
modulePaths.map((path) => [
|
|
86
|
+
path,
|
|
87
|
+
{
|
|
88
|
+
path,
|
|
89
|
+
dependencyPaths: [],
|
|
90
|
+
dependentPaths: [],
|
|
91
|
+
moduleDependencies: [],
|
|
92
|
+
},
|
|
93
|
+
]),
|
|
94
|
+
);
|
|
95
|
+
|
|
96
|
+
const getDependencyPaths = async (filePath: string) => {
|
|
97
|
+
const contents = await fs.readFile(filePath, 'utf8');
|
|
98
|
+
const importedPaths = isJavascriptModule(filePath)
|
|
99
|
+
? resolvePathAliases(getImportedModules(contents), path.dirname(filePath))
|
|
100
|
+
: [];
|
|
101
|
+
const importedPathsRelativeToDirectory = importedPaths.map(
|
|
102
|
+
(dependencyPath) => {
|
|
103
|
+
const isModulePath = !dependencyPath.startsWith('.');
|
|
104
|
+
|
|
105
|
+
/*
|
|
106
|
+
path.isAbsolute will return false if the path looks like JavaScript module imports
|
|
107
|
+
e.g. path.isAbsolute('react-dom/server') will return false, but for our purposes this
|
|
108
|
+
path is not a relative one.
|
|
109
|
+
*/
|
|
110
|
+
if (isModulePath || path.isAbsolute(dependencyPath)) {
|
|
111
|
+
return dependencyPath;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
let pathToDependencyFromDirectory = path.resolve(
|
|
115
|
+
/*
|
|
116
|
+
path.resolve resolves paths differently from what imports on javascript do.
|
|
117
|
+
|
|
118
|
+
So if we wouldn't do this, for a template at "/path/to/template.tsx" with a dependency path of "./other-template"
|
|
119
|
+
would end up going into /path/to/template.tsx/other-template instead of /path/to/other-template which is the
|
|
120
|
+
one the import is meant to go to
|
|
121
|
+
*/
|
|
122
|
+
path.dirname(filePath),
|
|
123
|
+
dependencyPath,
|
|
124
|
+
);
|
|
125
|
+
|
|
126
|
+
let isDirectory = false;
|
|
127
|
+
try {
|
|
128
|
+
// will throw if the the file is not existent
|
|
129
|
+
isDirectory = statSync(pathToDependencyFromDirectory).isDirectory();
|
|
130
|
+
} catch {
|
|
131
|
+
// do nothing
|
|
132
|
+
}
|
|
133
|
+
if (isDirectory) {
|
|
134
|
+
const pathToSubDirectory = pathToDependencyFromDirectory;
|
|
135
|
+
const pathWithExtension = checkFileExtensionsUntilItExists(
|
|
136
|
+
`${pathToSubDirectory}/index`,
|
|
137
|
+
);
|
|
138
|
+
if (pathWithExtension) {
|
|
139
|
+
pathToDependencyFromDirectory = pathWithExtension;
|
|
140
|
+
} else {
|
|
141
|
+
console.warn(
|
|
142
|
+
`Could not find index file for directory at ${pathToDependencyFromDirectory}. This is probably going to cause issues with both hot reloading and your code.`,
|
|
143
|
+
);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
const extension = path.extname(pathToDependencyFromDirectory);
|
|
148
|
+
const pathWithEnsuredExtension = (() => {
|
|
149
|
+
if (
|
|
150
|
+
extension.length > 0 &&
|
|
151
|
+
existsSync(pathToDependencyFromDirectory)
|
|
152
|
+
) {
|
|
153
|
+
return pathToDependencyFromDirectory;
|
|
154
|
+
}
|
|
155
|
+
if (javascriptExtensions.includes(extension)) {
|
|
156
|
+
return checkFileExtensionsUntilItExists(
|
|
157
|
+
pathToDependencyFromDirectory.replace(extension, ''),
|
|
158
|
+
);
|
|
159
|
+
}
|
|
160
|
+
return checkFileExtensionsUntilItExists(
|
|
161
|
+
pathToDependencyFromDirectory,
|
|
162
|
+
);
|
|
163
|
+
})();
|
|
164
|
+
|
|
165
|
+
if (pathWithEnsuredExtension) {
|
|
166
|
+
pathToDependencyFromDirectory = pathWithEnsuredExtension;
|
|
167
|
+
} else {
|
|
168
|
+
console.warn(
|
|
169
|
+
`Could not find file at ${pathToDependencyFromDirectory}`,
|
|
170
|
+
);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
return pathToDependencyFromDirectory;
|
|
174
|
+
},
|
|
175
|
+
);
|
|
176
|
+
|
|
177
|
+
const moduleDependencies = importedPathsRelativeToDirectory.filter(
|
|
178
|
+
(dependencyPath) =>
|
|
179
|
+
!dependencyPath.startsWith('.') && !path.isAbsolute(dependencyPath),
|
|
180
|
+
);
|
|
181
|
+
|
|
182
|
+
const nonNodeModuleImportPathsRelativeToDirectory =
|
|
183
|
+
importedPathsRelativeToDirectory.filter(
|
|
184
|
+
(dependencyPath) =>
|
|
185
|
+
dependencyPath.startsWith('.') || path.isAbsolute(dependencyPath),
|
|
186
|
+
);
|
|
187
|
+
|
|
188
|
+
return {
|
|
189
|
+
dependencyPaths: nonNodeModuleImportPathsRelativeToDirectory,
|
|
190
|
+
moduleDependencies,
|
|
191
|
+
};
|
|
192
|
+
};
|
|
193
|
+
|
|
194
|
+
const updateModuleDependenciesInGraph = async (moduleFilePath: string) => {
|
|
195
|
+
if (graph[moduleFilePath] === undefined) {
|
|
196
|
+
graph[moduleFilePath] = {
|
|
197
|
+
path: moduleFilePath,
|
|
198
|
+
dependencyPaths: [],
|
|
199
|
+
dependentPaths: [],
|
|
200
|
+
moduleDependencies: [],
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
const { moduleDependencies, dependencyPaths: newDependencyPaths } =
|
|
205
|
+
await getDependencyPaths(moduleFilePath);
|
|
206
|
+
|
|
207
|
+
graph[moduleFilePath].moduleDependencies = moduleDependencies;
|
|
208
|
+
|
|
209
|
+
// we go through these to remove the ones that don't exist anymore
|
|
210
|
+
for (const dependencyPath of graph[moduleFilePath].dependencyPaths) {
|
|
211
|
+
// Looping through only the ones that were on the dependencyPaths but are not
|
|
212
|
+
// in the newDependencyPaths
|
|
213
|
+
if (newDependencyPaths.includes(dependencyPath)) continue;
|
|
214
|
+
|
|
215
|
+
const dependencyModule = graph[dependencyPath];
|
|
216
|
+
if (dependencyModule !== undefined) {
|
|
217
|
+
dependencyModule.dependentPaths =
|
|
218
|
+
dependencyModule.dependentPaths.filter(
|
|
219
|
+
(dependentPath) => dependentPath !== moduleFilePath,
|
|
220
|
+
);
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
graph[moduleFilePath].dependencyPaths = newDependencyPaths;
|
|
225
|
+
|
|
226
|
+
for (const dependencyPath of newDependencyPaths) {
|
|
227
|
+
if (graph[dependencyPath] === undefined) {
|
|
228
|
+
/*
|
|
229
|
+
This import path might have not been initialized as it can be outside
|
|
230
|
+
of the original directory we looked into.
|
|
231
|
+
*/
|
|
232
|
+
await updateModuleDependenciesInGraph(dependencyPath);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
const dependencyModule = graph[dependencyPath];
|
|
236
|
+
|
|
237
|
+
if (dependencyModule === undefined) {
|
|
238
|
+
throw new Error(
|
|
239
|
+
`Loading the dependency path ${dependencyPath} did not initialize it at all. This is a bug in React PDF.`,
|
|
240
|
+
);
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
if (!dependencyModule.dependentPaths.includes(moduleFilePath)) {
|
|
244
|
+
dependencyModule.dependentPaths.push(moduleFilePath);
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
};
|
|
248
|
+
|
|
249
|
+
for (const filePath of modulePaths) {
|
|
250
|
+
await updateModuleDependenciesInGraph(filePath);
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
const removeModuleFromGraph = (filePath: string) => {
|
|
254
|
+
const module = graph[filePath];
|
|
255
|
+
if (module) {
|
|
256
|
+
for (const dependencyPath of module.dependencyPaths) {
|
|
257
|
+
if (graph[dependencyPath]) {
|
|
258
|
+
graph[dependencyPath].dependentPaths = graph[
|
|
259
|
+
dependencyPath
|
|
260
|
+
]!.dependentPaths.filter(
|
|
261
|
+
(dependentPath) => dependentPath !== filePath,
|
|
262
|
+
);
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
delete graph[filePath];
|
|
266
|
+
}
|
|
267
|
+
};
|
|
268
|
+
|
|
269
|
+
return [
|
|
270
|
+
graph,
|
|
271
|
+
async (event: EventName, pathToModified: string) => {
|
|
272
|
+
switch (event) {
|
|
273
|
+
case 'change':
|
|
274
|
+
if (isJavascriptModule(pathToModified)) {
|
|
275
|
+
await updateModuleDependenciesInGraph(pathToModified);
|
|
276
|
+
}
|
|
277
|
+
break;
|
|
278
|
+
case 'add':
|
|
279
|
+
if (isJavascriptModule(pathToModified)) {
|
|
280
|
+
await updateModuleDependenciesInGraph(pathToModified);
|
|
281
|
+
}
|
|
282
|
+
break;
|
|
283
|
+
case 'addDir': {
|
|
284
|
+
const filesInsideAddedDirectory =
|
|
285
|
+
await readAllFilesInsideDirectory(pathToModified);
|
|
286
|
+
const modulesInsideAddedDirectory =
|
|
287
|
+
filesInsideAddedDirectory.filter(isJavascriptModule);
|
|
288
|
+
for (const filePath of modulesInsideAddedDirectory) {
|
|
289
|
+
await updateModuleDependenciesInGraph(filePath);
|
|
290
|
+
}
|
|
291
|
+
break;
|
|
292
|
+
}
|
|
293
|
+
case 'unlink':
|
|
294
|
+
if (isJavascriptModule(pathToModified)) {
|
|
295
|
+
removeModuleFromGraph(pathToModified);
|
|
296
|
+
}
|
|
297
|
+
break;
|
|
298
|
+
case 'unlinkDir': {
|
|
299
|
+
const filesInsideDeletedDirectory =
|
|
300
|
+
await readAllFilesInsideDirectory(pathToModified);
|
|
301
|
+
const modulesInsideDeletedDirectory =
|
|
302
|
+
filesInsideDeletedDirectory.filter(isJavascriptModule);
|
|
303
|
+
for (const filePath of modulesInsideDeletedDirectory) {
|
|
304
|
+
removeModuleFromGraph(filePath);
|
|
305
|
+
}
|
|
306
|
+
break;
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
},
|
|
310
|
+
{
|
|
311
|
+
/**
|
|
312
|
+
* Resolves all modules that depend on the specified module, directly or indirectly.
|
|
313
|
+
*
|
|
314
|
+
* @param pathToModule - The path to the module whose dependents we want to find
|
|
315
|
+
* @returns An array of paths to all modules that depend on the specified module
|
|
316
|
+
*/
|
|
317
|
+
resolveDependentsOf: function resolveDependentsOf(
|
|
318
|
+
pathToModule: string,
|
|
319
|
+
): string[] {
|
|
320
|
+
const dependentPaths = new Set<string>();
|
|
321
|
+
const stack: string[] = [pathToModule];
|
|
322
|
+
|
|
323
|
+
while (stack.length > 0) {
|
|
324
|
+
const currentPath = stack.pop()!;
|
|
325
|
+
const moduleEntry = graph[currentPath];
|
|
326
|
+
|
|
327
|
+
if (!moduleEntry) continue;
|
|
328
|
+
|
|
329
|
+
for (const dependentPath of moduleEntry.dependentPaths) {
|
|
330
|
+
if (
|
|
331
|
+
dependentPaths.has(dependentPath) ||
|
|
332
|
+
dependentPath === pathToModule
|
|
333
|
+
)
|
|
334
|
+
continue;
|
|
335
|
+
|
|
336
|
+
dependentPaths.add(dependentPath);
|
|
337
|
+
stack.push(dependentPath);
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
return [...dependentPaths.values()];
|
|
342
|
+
},
|
|
343
|
+
},
|
|
344
|
+
] as const;
|
|
345
|
+
};
|