@cedarjs/cli 8.0.0-canary.3333 → 8.0.0-canary.3337
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.
|
@@ -14,6 +14,9 @@ const CONFIG_FILENAME = "deploy.toml";
|
|
|
14
14
|
const SYMLINK_FLAGS = "-nsf";
|
|
15
15
|
const CURRENT_RELEASE_SYMLINK_NAME = "current";
|
|
16
16
|
const LIFECYCLE_HOOKS = ["before", "after"];
|
|
17
|
+
const WEB_DIST_DIRS = ["web/dist", "web/dist/browser"];
|
|
18
|
+
const BUILD_MANIFEST_FILENAME = "client-build-manifest.json";
|
|
19
|
+
const LINK_BATCH_SIZE = 100;
|
|
17
20
|
const RELEASE_DIR_PATTERN = "^[0-9]{14}$";
|
|
18
21
|
const releaseDirRegExp = new RegExp(RELEASE_DIR_PATTERN);
|
|
19
22
|
const DEFAULT_SERVER_CONFIG = {
|
|
@@ -210,6 +213,135 @@ const commandWithLifecycleEvents = ({
|
|
|
210
213
|
tasks.push(lifecycleTask("after", name, skip, config));
|
|
211
214
|
return tasks.flat().filter((t) => Boolean(t));
|
|
212
215
|
};
|
|
216
|
+
const manifestAssetFiles = (manifestJson) => {
|
|
217
|
+
const manifest = JSON.parse(manifestJson);
|
|
218
|
+
const files = /* @__PURE__ */ new Set();
|
|
219
|
+
if (!manifest || typeof manifest !== "object") {
|
|
220
|
+
return [];
|
|
221
|
+
}
|
|
222
|
+
for (const entry of Object.values(manifest)) {
|
|
223
|
+
if (!entry || typeof entry !== "object") {
|
|
224
|
+
continue;
|
|
225
|
+
}
|
|
226
|
+
if ("file" in entry && typeof entry.file === "string") {
|
|
227
|
+
files.add(entry.file);
|
|
228
|
+
}
|
|
229
|
+
for (const key of ["css", "assets"]) {
|
|
230
|
+
if (!(key in entry) || !Array.isArray(entry[key])) {
|
|
231
|
+
continue;
|
|
232
|
+
}
|
|
233
|
+
for (const item of entry[key]) {
|
|
234
|
+
if (typeof item === "string") {
|
|
235
|
+
files.add(item);
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
return [...files];
|
|
241
|
+
};
|
|
242
|
+
const shellQuote = (value) => `'${value.replaceAll("'", `'\\''`)}'`;
|
|
243
|
+
const readBuildManifest = async (ssh, releasePath) => {
|
|
244
|
+
for (const distDir of WEB_DIST_DIRS) {
|
|
245
|
+
let stdout;
|
|
246
|
+
try {
|
|
247
|
+
;
|
|
248
|
+
({ stdout } = await ssh.exec(pathJoin(releasePath, distDir), "cat", [
|
|
249
|
+
BUILD_MANIFEST_FILENAME
|
|
250
|
+
]));
|
|
251
|
+
} catch {
|
|
252
|
+
continue;
|
|
253
|
+
}
|
|
254
|
+
const jsonStart = stdout.indexOf("{");
|
|
255
|
+
const json = jsonStart === -1 ? stdout : stdout.slice(jsonStart);
|
|
256
|
+
try {
|
|
257
|
+
return { distDir, files: manifestAssetFiles(json) };
|
|
258
|
+
} catch (e) {
|
|
259
|
+
const message = e instanceof Error ? e.message : String(e);
|
|
260
|
+
throw new Error(
|
|
261
|
+
`Could not parse ${pathJoin(releasePath, distDir, BUILD_MANIFEST_FILENAME)}: ${message}`
|
|
262
|
+
);
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
return void 0;
|
|
266
|
+
};
|
|
267
|
+
const keepPreviousAssets = async (yargs, ssh, serverConfig) => {
|
|
268
|
+
const newReleasePath = pathJoin(serverConfig.path, yargs.releaseDir);
|
|
269
|
+
let newRelease;
|
|
270
|
+
try {
|
|
271
|
+
newRelease = await readBuildManifest(ssh, newReleasePath);
|
|
272
|
+
} catch (e) {
|
|
273
|
+
const reason = e instanceof Error ? e.message : String(e);
|
|
274
|
+
return { status: "skipped", reason };
|
|
275
|
+
}
|
|
276
|
+
if (!newRelease) {
|
|
277
|
+
return {
|
|
278
|
+
status: "skipped",
|
|
279
|
+
reason: `No ${BUILD_MANIFEST_FILENAME} in ` + WEB_DIST_DIRS.map((dir) => pathJoin(yargs.releaseDir, dir)).join(
|
|
280
|
+
" or "
|
|
281
|
+
)
|
|
282
|
+
};
|
|
283
|
+
}
|
|
284
|
+
const { stdout } = await ssh.exec(serverConfig.path, "ls");
|
|
285
|
+
const previousReleases = stdout.split("\n").map((line) => line.trim()).filter((dir) => releaseDirRegExp.test(dir) && dir !== yargs.releaseDir).sort().reverse().slice(0, Math.max(serverConfig.keepReleases - 1, 0));
|
|
286
|
+
const presentFiles = new Set(newRelease.files);
|
|
287
|
+
const warnings = [];
|
|
288
|
+
let linkedCount = 0;
|
|
289
|
+
for (const release of previousReleases) {
|
|
290
|
+
const releasePath = pathJoin(serverConfig.path, release);
|
|
291
|
+
let previousRelease;
|
|
292
|
+
try {
|
|
293
|
+
previousRelease = await readBuildManifest(ssh, releasePath);
|
|
294
|
+
} catch (e) {
|
|
295
|
+
const message = e instanceof Error ? e.message : String(e);
|
|
296
|
+
warnings.push(`Skipping the web assets of release ${release}: ${message}`);
|
|
297
|
+
continue;
|
|
298
|
+
}
|
|
299
|
+
if (!previousRelease) {
|
|
300
|
+
warnings.push(
|
|
301
|
+
`Release ${release} has no web build manifest, skipping its assets`
|
|
302
|
+
);
|
|
303
|
+
continue;
|
|
304
|
+
}
|
|
305
|
+
const missingFiles = previousRelease.files.filter(
|
|
306
|
+
(file) => !presentFiles.has(file)
|
|
307
|
+
);
|
|
308
|
+
if (missingFiles.length === 0) {
|
|
309
|
+
continue;
|
|
310
|
+
}
|
|
311
|
+
const filesByDir = /* @__PURE__ */ new Map();
|
|
312
|
+
for (const file of missingFiles) {
|
|
313
|
+
const dir = path.posix.dirname(file);
|
|
314
|
+
filesByDir.set(dir, [...filesByDir.get(dir) ?? [], file]);
|
|
315
|
+
}
|
|
316
|
+
try {
|
|
317
|
+
for (const [dir, files] of filesByDir) {
|
|
318
|
+
const targetDir = pathJoin(newReleasePath, newRelease.distDir, dir);
|
|
319
|
+
await ssh.exec(newReleasePath, "mkdir", ["-p", shellQuote(targetDir)]);
|
|
320
|
+
for (let i = 0; i < files.length; i += LINK_BATCH_SIZE) {
|
|
321
|
+
const sources = files.slice(i, i + LINK_BATCH_SIZE).map(
|
|
322
|
+
(file) => shellQuote(pathJoin(releasePath, previousRelease.distDir, file))
|
|
323
|
+
);
|
|
324
|
+
await ssh.exec(newReleasePath, "ln", [
|
|
325
|
+
"-f",
|
|
326
|
+
...sources,
|
|
327
|
+
shellQuote(targetDir)
|
|
328
|
+
]);
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
} catch (e) {
|
|
332
|
+
const message = e instanceof Error ? e.message : String(e);
|
|
333
|
+
warnings.push(
|
|
334
|
+
`Could not keep the web assets of release ${release}: ${message}`
|
|
335
|
+
);
|
|
336
|
+
continue;
|
|
337
|
+
}
|
|
338
|
+
for (const file of missingFiles) {
|
|
339
|
+
presentFiles.add(file);
|
|
340
|
+
}
|
|
341
|
+
linkedCount += missingFiles.length;
|
|
342
|
+
}
|
|
343
|
+
return { status: "done", linkedCount, warnings };
|
|
344
|
+
};
|
|
213
345
|
const deployTasks = (yargs, ssh, serverConfig, serverLifecycle) => {
|
|
214
346
|
const cmdPath = pathJoin(serverConfig.path, yargs.releaseDir);
|
|
215
347
|
const config = {
|
|
@@ -358,6 +490,30 @@ const deployTasks = (yargs, ssh, serverConfig, serverLifecycle) => {
|
|
|
358
490
|
})
|
|
359
491
|
);
|
|
360
492
|
}
|
|
493
|
+
tasks.push(
|
|
494
|
+
...commandWithLifecycleEvents({
|
|
495
|
+
name: "keepAssets",
|
|
496
|
+
config,
|
|
497
|
+
skip: !yargs.keepAssets || !yargs.build || !serverConfig.sides.includes("web"),
|
|
498
|
+
command: {
|
|
499
|
+
title: `Keeping web assets of previous releases...`,
|
|
500
|
+
task: async (_ctx, task) => {
|
|
501
|
+
const result = await keepPreviousAssets(yargs, ssh, serverConfig);
|
|
502
|
+
if (result.status === "skipped") {
|
|
503
|
+
return task.skip(
|
|
504
|
+
c.warning(
|
|
505
|
+
`Warning: ${result.reason}, not keeping the web assets of previous releases`
|
|
506
|
+
)
|
|
507
|
+
);
|
|
508
|
+
}
|
|
509
|
+
task.output = [
|
|
510
|
+
`Linked ${result.linkedCount} file(s) from previous releases`,
|
|
511
|
+
...result.warnings.map((warning) => c.warning(warning))
|
|
512
|
+
].join("\n");
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
})
|
|
516
|
+
);
|
|
361
517
|
tasks.push(
|
|
362
518
|
...commandWithLifecycleEvents({
|
|
363
519
|
name: "symlinkCurrent",
|
|
@@ -599,8 +755,10 @@ export {
|
|
|
599
755
|
commands,
|
|
600
756
|
deployTasks,
|
|
601
757
|
handler,
|
|
758
|
+
keepPreviousAssets,
|
|
602
759
|
lifecycleTask,
|
|
603
760
|
maintenanceTasks,
|
|
761
|
+
manifestAssetFiles,
|
|
604
762
|
parseConfig,
|
|
605
763
|
rollbackTasks,
|
|
606
764
|
serverConfigWithDefaults,
|
|
@@ -48,6 +48,11 @@ const builder = (yargs) => {
|
|
|
48
48
|
default: true,
|
|
49
49
|
type: "boolean"
|
|
50
50
|
});
|
|
51
|
+
yargs.option("keep-assets", {
|
|
52
|
+
describe: "Hardlink previous releases' hashed web assets into the new release so open browser tabs keep working after the deploy",
|
|
53
|
+
default: true,
|
|
54
|
+
type: "boolean"
|
|
55
|
+
});
|
|
51
56
|
yargs.option("releaseDir", {
|
|
52
57
|
describe: "Directory to create for the latest release, defaults to timestamp",
|
|
53
58
|
default: (/* @__PURE__ */ new Date()).toISOString().replace(/[:\-TZ]/g, "").replace(/\.\d+$/, ""),
|
|
@@ -95,6 +100,7 @@ async function handler(yargs) {
|
|
|
95
100
|
build: yargs.build,
|
|
96
101
|
restart: yargs.restart,
|
|
97
102
|
cleanup: yargs.cleanup,
|
|
103
|
+
keepAssets: yargs.keepAssets,
|
|
98
104
|
maintenance: yargs.maintenance,
|
|
99
105
|
rollback: yargs.rollback,
|
|
100
106
|
verbose: yargs.verbose,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cedarjs/cli",
|
|
3
|
-
"version": "8.0.0-canary.
|
|
3
|
+
"version": "8.0.0-canary.3337",
|
|
4
4
|
"description": "The CedarJS Command Line",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -36,17 +36,17 @@
|
|
|
36
36
|
"@babel/preset-typescript": "7.29.7",
|
|
37
37
|
"@babel/traverse": "7.29.8",
|
|
38
38
|
"@babel/types": "7.29.8",
|
|
39
|
-
"@cedarjs/api-server": "8.0.0-canary.
|
|
40
|
-
"@cedarjs/babel-config": "8.0.0-canary.
|
|
41
|
-
"@cedarjs/cli-helpers": "8.0.0-canary.
|
|
42
|
-
"@cedarjs/internal": "8.0.0-canary.
|
|
43
|
-
"@cedarjs/prerender": "8.0.0-canary.
|
|
44
|
-
"@cedarjs/project-config": "8.0.0-canary.
|
|
45
|
-
"@cedarjs/structure": "8.0.0-canary.
|
|
46
|
-
"@cedarjs/telemetry": "8.0.0-canary.
|
|
47
|
-
"@cedarjs/utils": "8.0.0-canary.
|
|
48
|
-
"@cedarjs/vite": "8.0.0-canary.
|
|
49
|
-
"@cedarjs/web-server": "8.0.0-canary.
|
|
39
|
+
"@cedarjs/api-server": "8.0.0-canary.3337",
|
|
40
|
+
"@cedarjs/babel-config": "8.0.0-canary.3337",
|
|
41
|
+
"@cedarjs/cli-helpers": "8.0.0-canary.3337",
|
|
42
|
+
"@cedarjs/internal": "8.0.0-canary.3337",
|
|
43
|
+
"@cedarjs/prerender": "8.0.0-canary.3337",
|
|
44
|
+
"@cedarjs/project-config": "8.0.0-canary.3337",
|
|
45
|
+
"@cedarjs/structure": "8.0.0-canary.3337",
|
|
46
|
+
"@cedarjs/telemetry": "8.0.0-canary.3337",
|
|
47
|
+
"@cedarjs/utils": "8.0.0-canary.3337",
|
|
48
|
+
"@cedarjs/vite": "8.0.0-canary.3337",
|
|
49
|
+
"@cedarjs/web-server": "8.0.0-canary.3337",
|
|
50
50
|
"@listr2/prompt-adapter-enquirer": "4.3.3",
|
|
51
51
|
"@opentelemetry/api": "1.9.1",
|
|
52
52
|
"@opentelemetry/core": "2.11.0",
|
|
@@ -94,7 +94,7 @@
|
|
|
94
94
|
"yargs": "17.7.3"
|
|
95
95
|
},
|
|
96
96
|
"devDependencies": {
|
|
97
|
-
"@cedarjs/framework-tools": "8.0.0-canary.
|
|
97
|
+
"@cedarjs/framework-tools": "8.0.0-canary.3337",
|
|
98
98
|
"@prisma/dmmf": "7.10.0",
|
|
99
99
|
"@types/archiver": "^7.0.0",
|
|
100
100
|
"memfs": "4.80.0",
|