-
Notifications
You must be signed in to change notification settings - Fork 4
/
bundle.ts
143 lines (124 loc) · 4.34 KB
/
bundle.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
import { Argv } from "yargs";
import { copySync, existsSync } from "fs-extra";
import { join, normalize } from "path";
import { eachSeries } from "async";
import packlist = require("npm-packlist");
import { Package } from "../Package";
import { TspScriptsOptions, tspScriptsOptions } from "../tspScriptsOptions";
import { tspHandler } from "../tspHandler";
import {
CliError,
cliOptions,
log,
PackageNotFoundError,
} from "@jtbennett/ts-project-cli-utils";
import { getPaths } from "../paths";
import { execSync } from "child_process";
const NODE_MODULES = "node_modules";
const copyNodeModules = (src: string, dest: string, skip?: Package[]) => {
if (existsSync(src)) {
log.verbose(` Copying ${src} to ${dest}`);
// Don't copy workspaces -- they're handled separately.
const filter =
skip && skip?.length > 0
? (src: string) =>
!skip.some((pkg) =>
src.endsWith(join(NODE_MODULES, normalize(pkg.name))),
)
: undefined;
copySync(src, dest, { overwrite: false, errorOnExist: true, filter });
} else {
log.verbose(` Directory to copy does not exist: "${src}"`);
}
};
const copyWorkspaceFiles = async (workspace: Package, dest: string) => {
log.info(`Copying ${workspace.name} to ${dest}`);
// Copy the files that would be included if the package were published.
const filesToCopy = await packlist({ path: workspace.path });
filesToCopy.forEach((file) => {
log.verbose(` Copying ${file}`);
copySync(join(workspace.path, file), join(dest, file));
});
// Copy node_modules, which will contain only the dependencies for which
// this workspace requires a different version than another workspace.
copyNodeModules(join(workspace.path, NODE_MODULES), join(dest, NODE_MODULES));
};
const handler = tspHandler<
TspScriptsOptions & { appName: string; outDir: string }
>(async (args) => {
const allPackages = Package.loadAll();
const app = allPackages.find(
(pkg) => pkg.packageJson && pkg.packageJson.name === args.appName,
);
if (!app) {
throw new PackageNotFoundError(args.appName);
}
if (existsSync(args.outDir)) {
throw new CliError(`Out directory already exists: ${args.outDir}`);
}
// Remove all devDependencies, plus all dependencies not used by this app.
// This has to be done inside bundle, because ts-project-scripts itself is a
// devDependency, and will be removed by this command.
if (args.yarn) {
log.info(
`Removing dependencies not used by ${args.appName} and all devDependencies.`,
);
execSync(`yarn workspaces focus --production ${args.appName}`, {
stdio: "inherit",
});
} else {
log.warn(
"Yarn was not run. node_modules in --out-dir may be missing required dependencies and/or " +
"include devDependencies or other unnecessary dependencies.",
);
}
// Copy root node_modules, excluding workspace symlinks.
copyNodeModules(
join(getPaths().rootPath, NODE_MODULES),
join(args.outDir, NODE_MODULES),
allPackages,
);
// Copy the app's files.
await copyWorkspaceFiles(app, join(args.outDir, app.dir));
// Copy files from workspaces the app depends on.
const workspacesToCopy = new Map<string, Package>();
const addWorkspacesToCopy = (pkg: Package) => {
if (!workspacesToCopy.has(pkg.name)) {
pkg.loadDependencies().forEach((dep) => {
workspacesToCopy.set(dep.name, dep);
addWorkspacesToCopy(dep);
});
}
};
addWorkspacesToCopy(app);
// eslint-disable-next-line @typescript-eslint/no-misused-promises
await eachSeries(workspacesToCopy.values(), async (workspace) => {
await copyWorkspaceFiles(
workspace,
join(args.outDir, NODE_MODULES, normalize(workspace.name)),
);
});
// Don't rerun yarn on CI server.
args.yarn = args.yarn && process.env.CI !== "true";
});
export const bundle = {
command: "bundle <app-name>",
describe: "Prepare an app for deployment",
builder: (yargs: Argv) =>
yargs
.usage("Usage: $0 bundle <app-name> --out-dir <out-dir>")
.positional("app-name", {
desc: "Name of the app to bundle.",
type: "string",
})
.options({
outDir: {
alias: "o",
describe: "Path where the bundled application will be output.",
demand: true,
},
...cliOptions,
...tspScriptsOptions,
}),
handler,
};