@digigov/cli-build 2.0.0-36b707c1 → 2.0.0-385c7994

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/copy-files.js CHANGED
@@ -1,125 +1,106 @@
1
- /* eslint-disable no-console */
2
- const path = require("path");
3
- const fs = require("fs");
4
- const fse = require("fs-extra");
5
- const glob = require("glob");
6
- const { resolveProject } = require("@digigov/cli/lib");
1
+ import { logger, resolveProject } from '@digigov/cli/lib';
2
+ import fs from 'fs-extra';
3
+ import path from 'path';
7
4
 
8
- const project = resolveProject();
9
5
  const packagePath = process.cwd();
6
+ const project = resolveProject();
10
7
  const buildPath = path.join(project.root, project.distDir);
11
8
 
12
- async function includeFileInBuild(file) {
9
+ /**
10
+ * Copy a file from the package to the build directory
11
+ *
12
+ * @param {string} file - The file to include in the build
13
+ */
14
+ function includeFileInBuild(file) {
13
15
  const sourcePath = path.resolve(packagePath, file);
14
16
  const targetPath = path.resolve(buildPath, path.basename(file));
15
- await fse.copy(sourcePath, targetPath);
16
- console.log(`Copied ${sourcePath} to ${targetPath}`);
17
+ fs.copySync(sourcePath, targetPath);
18
+ logger.debug(`Copied ${sourcePath} to build directory`);
17
19
  }
18
20
 
19
- async function createRootPackageFile() {
20
- const packageData = await fse.readFile(
21
- path.resolve(packagePath, "./package.json"),
22
- "utf8",
21
+ /**
22
+ * Create a package.json file in the build directory
23
+ */
24
+ function createRootPackageFile() {
25
+ const packageData = fs.readFileSync(
26
+ path.resolve(packagePath, './package.json'),
27
+ 'utf8'
23
28
  );
29
+
30
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
24
31
  const { nyc, scripts, devDependencies, workspaces, ...packageDataOther } =
25
32
  JSON.parse(packageData);
26
33
  const newPackageData = {
27
34
  ...packageDataOther,
28
35
  private: false,
29
- main: "./cjs/index.js",
30
- module: "./index.js",
31
- typings: "./index.d.ts",
36
+ exports: undefined,
37
+ main: 'index.js',
38
+ module: 'index.js',
39
+ type: 'module', // ESM only
32
40
  };
33
- const targetPath = path.resolve(buildPath, "./package.json");
41
+ const targetPath = path.resolve(buildPath, './package.json');
34
42
 
35
- await fse.writeFile(
36
- targetPath,
37
- JSON.stringify(newPackageData, null, 2),
38
- "utf8",
39
- );
40
- console.log(`Created package.json in ${targetPath}`);
43
+ fs.writeFileSync(targetPath, JSON.stringify(newPackageData, null, 2), 'utf8');
44
+ logger.debug(`Created package.json in build directory`);
41
45
 
42
46
  return newPackageData;
43
47
  }
44
48
 
45
- function createNestedPackageFiles() {
46
- const indexPaths = glob.sync(path.join(buildPath, "!(cjs)/**/index.js"));
47
-
48
- indexPaths.forEach((indexPath) => {
49
- if (indexPath === path.join(buildPath, "index.js")) return;
50
- const packageData = {
51
- sideEffects: false,
52
- module: "./index.js",
53
- types: "./index.d.ts",
54
- main: path.relative(
55
- path.dirname(indexPath),
56
- indexPath.replace(buildPath, path.join(buildPath, "/cjs")),
57
- ),
58
- };
59
- fs.writeFileSync(
60
- path.join(path.dirname(indexPath), "package.json"),
61
- JSON.stringify(packageData, null, 2),
62
- );
63
- });
64
- }
65
-
66
- async function prepend(file, string) {
67
- const data = await fse.readFile(file, "utf8");
68
- await fse.writeFile(file, string + data, "utf8");
49
+ /**
50
+ * Prepend a string to a file
51
+ *
52
+ * @param {string} file - The file to prepend to
53
+ * @param {string} string - The string to prepend
54
+ */
55
+ function prepend(file, string) {
56
+ const data = fs.readFileSync(file, 'utf8');
57
+ fs.writeFileSync(file, string + data, 'utf8');
58
+ logger.debug(`Prepended license to ${file}`);
69
59
  }
70
60
 
71
- async function addLicense(packageData) {
72
- const license = `/** @license Digigov v${packageData.version}
61
+ /**
62
+ * Add license to the top of the files
63
+ *
64
+ * @param {object} packageData - The package data
65
+ */
66
+ function addLicense(packageData) {
67
+ const license = `/** @license Digigov v${packageData['version']}
73
68
  *
74
69
  * This source code is licensed under the BSD-2-Clause license found in the
75
70
  * LICENSE file in the root directory of this source tree.
76
71
  */
77
72
  `;
78
- await Promise.all(
79
- ["./index.js", "./index.mjs"].map(async (file) => {
80
- try {
81
- await prepend(path.resolve(buildPath, file), license);
82
- } catch (err) {
83
- if (err.code === "ENOENT") {
84
- console.log(`Skipped license for ${file}`);
85
- } else {
86
- throw err;
87
- }
73
+ ['./index.js', './index.mjs'].map(async (file) => {
74
+ try {
75
+ prepend(path.resolve(buildPath, file), license);
76
+ } catch (err) {
77
+ if (
78
+ typeof err === 'object' &&
79
+ err &&
80
+ 'code' in err &&
81
+ err.code === 'ENOENT'
82
+ ) {
83
+ logger.debug(`Skipped license for ${file}`);
84
+ } else {
85
+ throw err;
88
86
  }
89
- }),
90
- );
91
- }
92
-
93
- function createSeparateIndexModules() {
94
- const files = glob.sync(path.join(buildPath, "**/!(index).js"));
95
-
96
- files.forEach((file) => {
97
- fs.mkdirSync(file.replace(".js", ""));
98
- fs.renameSync(file, file.replace(".js", "/index.js"));
87
+ }
99
88
  });
100
89
  }
101
90
 
102
- async function run() {
103
- try {
104
- const packageData = await createRootPackageFile();
105
- createSeparateIndexModules();
106
- createNestedPackageFiles();
107
-
108
- await Promise.all(
109
- [
110
- // use enhanced readme from workspace root for `@digigov/ui`
111
- // packageData.name === '@digigov/ui' ? '../../README.md' : './README.md',
112
- "./src",
113
- "./README.md",
114
- "./CHANGELOG.md",
115
- "../../LICENSE",
116
- ].map((file) => includeFileInBuild(file)),
117
- );
91
+ /**
92
+ * Run the copy files script
93
+ */
94
+ export default function run() {
95
+ const packageData = createRootPackageFile();
118
96
 
119
- await addLicense(packageData);
120
- } catch (err) {
121
- console.error(err);
122
- }
97
+ [
98
+ // use enhanced readme from workspace root for `@digigov/ui`
99
+ // packageData.name === '@digigov/ui' ? '../../README.md' : './README.md',
100
+ './src',
101
+ './README.md',
102
+ './CHANGELOG.md',
103
+ '../../LICENSE',
104
+ ].map((file) => includeFileInBuild(file));
105
+ addLicense(packageData);
123
106
  }
124
-
125
- run();
@@ -0,0 +1,3 @@
1
+ import config from '@digigov/cli-lint/eslint.config';
2
+
3
+ export default [...config];
@@ -0,0 +1,215 @@
1
+ import { logger } from '@digigov/cli/lib';
2
+ import path from 'path';
3
+ import fs from 'fs-extra';
4
+ import { SyntaxKind, Project as TsMorphProject } from 'ts-morph';
5
+ import assert from 'assert';
6
+
7
+ import { getProjectTsconfig } from './common.js';
8
+
9
+ /** @typedef {Object} Project - Represents the project to be built
10
+ * @property {string} root - The project root directory
11
+ * @property {string} name - The project name as in package.json
12
+ * @property {string} src - The project src directory
13
+ * @property {string} distDir - The project build directory
14
+ */
15
+
16
+ /**
17
+ * Generate registry file for the given project
18
+ *
19
+ * @param {Project} project - The project object
20
+ * @param {string} [registryFilename="registry.js"] - The name of the registry file
21
+ * @param {string[]} absoluteFilePaths - The absolute paths of the files to include in the registry
22
+ * @returns {Promise<string>} - The path to the generated registry file
23
+ */
24
+ export async function generateRegistry(
25
+ project,
26
+ absoluteFilePaths,
27
+ registryFilename = 'registry.js'
28
+ ) {
29
+ const registryPath = ensureRegistryPath(project, registryFilename);
30
+
31
+ const relativePaths = absoluteFilePaths.map((path) => {
32
+ assert(
33
+ path.startsWith(project.root),
34
+ 'Expected path to be in project root'
35
+ );
36
+ return toNodeResolvablePath(
37
+ path.replace(`${project.root}/src/`, `${project.name}/`)
38
+ );
39
+ });
40
+ let registryPaths = relativePaths.map((path) => ({
41
+ path,
42
+ uid: createUid(path),
43
+ }));
44
+
45
+ if (registryPaths.length === 0)
46
+ throw new Error(
47
+ 'Could not generate registry. No exportable modules found.'
48
+ );
49
+
50
+ const importStatements = registryPaths.map(
51
+ (file) => `import * as ${file.uid} from "${file.path}";`
52
+ );
53
+ const componentsToExport = registryPaths.map(
54
+ (file) => ` '${file.path}': lazyImport(${file.uid})`
55
+ );
56
+
57
+ logger.debug(
58
+ `Including ${componentsToExport.length} items in ${registryPath}`
59
+ );
60
+
61
+ let registryFileContent = `
62
+ ${importStatements.join('\n')}
63
+ function lazyImport(pkgImport) {
64
+ return new Proxy(
65
+ {},
66
+ {
67
+ get: (_target, name) => {
68
+ if (name === '__esModule' || name === 'default') {
69
+ return pkgImport.default;
70
+ } else if(
71
+ name === '*'
72
+ ) {
73
+ return pkgImport;
74
+ } else {
75
+ return pkgImport[name];
76
+ }
77
+ },
78
+ }
79
+ )
80
+ }
81
+ export default {
82
+ ${componentsToExport.join(',\n')}
83
+ };
84
+ `;
85
+ await fs.writeFile(registryPath, registryFileContent);
86
+
87
+ return registryPath;
88
+ }
89
+
90
+ /**
91
+ * Generate a lazy registry file for the given project
92
+ *
93
+ * @param {Project} project - The project object
94
+ * @param {string[]} filePaths - The files whose exports will be included in the lazy registry
95
+ * @param {string} [lazyFilename="lazy.js"] - The name of the registry file
96
+ * @returns {Promise<string>} - The path to the generated lazy registry file
97
+ */
98
+ export async function generateLazyRegistry(
99
+ project,
100
+ filePaths,
101
+ lazyFilename = 'lazy.js'
102
+ ) {
103
+ const lazyPath = ensureRegistryPath(project, lazyFilename);
104
+
105
+ const tsMorphProject = new TsMorphProject({
106
+ tsConfigFilePath: getProjectTsconfig(project.root),
107
+ });
108
+
109
+ /** @type {Record<string, string>} */
110
+ let allComponents = {};
111
+
112
+ for (const filePath of filePaths) {
113
+ const sourceFile = tsMorphProject.addSourceFileAtPath(filePath);
114
+ const exports = sourceFile
115
+ .getExportSymbols()
116
+ .filter(isJsExport)
117
+ .map((symbol) => symbol.getName());
118
+
119
+ for (const exportedComponent of exports) {
120
+ if (
121
+ exportedComponent !== 'default' &&
122
+ exportedComponent.match(/^[A-Z][a-z]+/)
123
+ ) {
124
+ if (
125
+ !allComponents[exportedComponent] ||
126
+ allComponents[exportedComponent].length < filePath.length // Make import path more specific
127
+ ) {
128
+ allComponents[exportedComponent] = toNodeResolvablePath(
129
+ filePath.replace(`${project.root}/src/`, `${project.name}/`)
130
+ );
131
+ }
132
+ }
133
+ }
134
+ }
135
+
136
+ const componentCount = Object.keys(allComponents).length;
137
+
138
+ if (componentCount === 0)
139
+ throw new Error(
140
+ 'Could not generate lazy registry. No exportable components found.'
141
+ );
142
+
143
+ logger.debug(`Including ${componentCount} components in ${lazyPath}`);
144
+
145
+ const componentsToExport = Object.entries(allComponents)
146
+ .map(
147
+ ([component, filePath]) =>
148
+ ` '${component}': lazy(() => import('${filePath}').then((module) => ({ default: module['${component}'] })))`
149
+ )
150
+ .join(',\n');
151
+
152
+ const lazyFileContent = `import { lazy } from 'react';
153
+ export default {
154
+ ${componentsToExport}
155
+ };
156
+ `;
157
+
158
+ await fs.writeFile(lazyPath, lazyFileContent);
159
+
160
+ return lazyPath;
161
+ }
162
+
163
+ /**
164
+ * Ensure that the registry file does not already exist at the given path
165
+ *
166
+ * @param {Project} project - The project object
167
+ * @param {string} fileName - The name of the registry file
168
+ */
169
+ function ensureRegistryPath(project, fileName) {
170
+ const registryPath = path.join(project.root, project.src, fileName);
171
+ if (fs.existsSync(registryPath))
172
+ throw new Error(`A "${fileName}" file already exists at ${registryPath}.`);
173
+ return registryPath;
174
+ }
175
+
176
+ /**
177
+ * Extract a node-resolvable path
178
+ *
179
+ * @param {string} inputPath - The file path
180
+ * @returns {string} - The node-resolvable path
181
+ */
182
+ function toNodeResolvablePath(inputPath) {
183
+ const dir = path.dirname(inputPath);
184
+ const base = path.basename(inputPath, path.extname(inputPath));
185
+
186
+ return base === 'index' ? dir : path.join(dir, base);
187
+ }
188
+
189
+ /**
190
+ * Create a UID from a path
191
+ *
192
+ * @param {string} inputPath - The path
193
+ * @returns {string} - The UID
194
+ */
195
+ function createUid(inputPath) {
196
+ return inputPath.replace(/[/@\-.]/g, '_');
197
+ }
198
+
199
+ /**
200
+ * Check if a symbol is a JS export
201
+ *
202
+ * @param {import("ts-morph").Symbol} symbol - The symbol to check
203
+ */
204
+ function isJsExport(symbol) {
205
+ const declarations = symbol.getDeclarations();
206
+ return declarations.some((declaration) => {
207
+ const kind = declaration.getKind();
208
+ return (
209
+ kind === SyntaxKind.FunctionDeclaration ||
210
+ kind === SyntaxKind.ClassDeclaration ||
211
+ kind === SyntaxKind.VariableDeclaration ||
212
+ kind === SyntaxKind.ExportSpecifier
213
+ );
214
+ });
215
+ }