@meltemi/diagrams-cli 0.0.0-stage → 1.9.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/LICENSE +21 -0
- package/README.md +58 -2
- package/dist/assets-command.d.ts +16 -0
- package/dist/assets-command.d.ts.map +1 -0
- package/dist/assets-command.js +145 -0
- package/dist/assets-command.js.map +1 -0
- package/dist/assist-command.d.ts +19 -0
- package/dist/assist-command.d.ts.map +1 -0
- package/dist/assist-command.js +268 -0
- package/dist/assist-command.js.map +1 -0
- package/dist/auth-command.d.ts +14 -0
- package/dist/auth-command.d.ts.map +1 -0
- package/dist/auth-command.js +233 -0
- package/dist/auth-command.js.map +1 -0
- package/dist/bin.d.ts +3 -0
- package/dist/bin.d.ts.map +1 -0
- package/dist/bin.js +60 -0
- package/dist/bin.js.map +1 -0
- package/dist/build-command.d.ts +23 -0
- package/dist/build-command.d.ts.map +1 -0
- package/dist/build-command.js +123 -0
- package/dist/build-command.js.map +1 -0
- package/dist/cli.d.ts +46 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +823 -0
- package/dist/cli.js.map +1 -0
- package/dist/cloud-client.d.ts +246 -0
- package/dist/cloud-client.d.ts.map +1 -0
- package/dist/cloud-client.js +591 -0
- package/dist/cloud-client.js.map +1 -0
- package/dist/cloud-command.d.ts +56 -0
- package/dist/cloud-command.d.ts.map +1 -0
- package/dist/cloud-command.js +81 -0
- package/dist/cloud-command.js.map +1 -0
- package/dist/cloud-context.d.ts +106 -0
- package/dist/cloud-context.d.ts.map +1 -0
- package/dist/cloud-context.js +185 -0
- package/dist/cloud-context.js.map +1 -0
- package/dist/cloud-errors.d.ts +37 -0
- package/dist/cloud-errors.d.ts.map +1 -0
- package/dist/cloud-errors.js +46 -0
- package/dist/cloud-errors.js.map +1 -0
- package/dist/cloud-host.d.ts +72 -0
- package/dist/cloud-host.d.ts.map +1 -0
- package/dist/cloud-host.js +129 -0
- package/dist/cloud-host.js.map +1 -0
- package/dist/cloud-manifest.d.ts +12 -0
- package/dist/cloud-manifest.d.ts.map +1 -0
- package/dist/cloud-manifest.js +21 -0
- package/dist/cloud-manifest.js.map +1 -0
- package/dist/cloud-paths.d.ts +48 -0
- package/dist/cloud-paths.d.ts.map +1 -0
- package/dist/cloud-paths.js +104 -0
- package/dist/cloud-paths.js.map +1 -0
- package/dist/credentials.d.ts +107 -0
- package/dist/credentials.d.ts.map +1 -0
- package/dist/credentials.js +243 -0
- package/dist/credentials.js.map +1 -0
- package/dist/export-command.d.ts +35 -0
- package/dist/export-command.d.ts.map +1 -0
- package/dist/export-command.js +524 -0
- package/dist/export-command.js.map +1 -0
- package/dist/import-command.d.ts +20 -0
- package/dist/import-command.d.ts.map +1 -0
- package/dist/import-command.js +254 -0
- package/dist/import-command.js.map +1 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -0
- package/dist/init-command.d.ts +28 -0
- package/dist/init-command.d.ts.map +1 -0
- package/dist/init-command.js +199 -0
- package/dist/init-command.js.map +1 -0
- package/dist/login-listener.d.ts +58 -0
- package/dist/login-listener.d.ts.map +1 -0
- package/dist/login-listener.js +173 -0
- package/dist/login-listener.js.map +1 -0
- package/dist/messages/en.d.ts +533 -0
- package/dist/messages/en.d.ts.map +1 -0
- package/dist/messages/en.js +803 -0
- package/dist/messages/en.js.map +1 -0
- package/dist/messages/es.d.ts +3 -0
- package/dist/messages/es.d.ts.map +1 -0
- package/dist/messages/es.js +764 -0
- package/dist/messages/es.js.map +1 -0
- package/dist/messages/index.d.ts +442 -0
- package/dist/messages/index.d.ts.map +1 -0
- package/dist/messages/index.js +42 -0
- package/dist/messages/index.js.map +1 -0
- package/dist/messages/push-pull-en.d.ts +84 -0
- package/dist/messages/push-pull-en.d.ts.map +1 -0
- package/dist/messages/push-pull-en.js +146 -0
- package/dist/messages/push-pull-en.js.map +1 -0
- package/dist/messages/push-pull-es.d.ts +7 -0
- package/dist/messages/push-pull-es.d.ts.map +1 -0
- package/dist/messages/push-pull-es.js +144 -0
- package/dist/messages/push-pull-es.js.map +1 -0
- package/dist/open-url.d.ts +11 -0
- package/dist/open-url.d.ts.map +1 -0
- package/dist/open-url.js +36 -0
- package/dist/open-url.js.map +1 -0
- package/dist/plugins-command.d.ts +15 -0
- package/dist/plugins-command.d.ts.map +1 -0
- package/dist/plugins-command.js +106 -0
- package/dist/plugins-command.js.map +1 -0
- package/dist/preview.d.ts +54 -0
- package/dist/preview.d.ts.map +1 -0
- package/dist/preview.js +249 -0
- package/dist/preview.js.map +1 -0
- package/dist/project-errors.d.ts +17 -0
- package/dist/project-errors.d.ts.map +1 -0
- package/dist/project-errors.js +85 -0
- package/dist/project-errors.js.map +1 -0
- package/dist/projects-command.d.ts +13 -0
- package/dist/projects-command.d.ts.map +1 -0
- package/dist/projects-command.js +85 -0
- package/dist/projects-command.js.map +1 -0
- package/dist/publish-command.d.ts +41 -0
- package/dist/publish-command.d.ts.map +1 -0
- package/dist/publish-command.js +311 -0
- package/dist/publish-command.js.map +1 -0
- package/dist/pull-command.d.ts +21 -0
- package/dist/pull-command.d.ts.map +1 -0
- package/dist/pull-command.js +300 -0
- package/dist/pull-command.js.map +1 -0
- package/dist/push-command.d.ts +31 -0
- package/dist/push-command.d.ts.map +1 -0
- package/dist/push-command.js +391 -0
- package/dist/push-command.js.map +1 -0
- package/dist/push-review.d.ts +16 -0
- package/dist/push-review.d.ts.map +1 -0
- package/dist/push-review.js +72 -0
- package/dist/push-review.js.map +1 -0
- package/dist/push-set.d.ts +126 -0
- package/dist/push-set.d.ts.map +1 -0
- package/dist/push-set.js +289 -0
- package/dist/push-set.js.map +1 -0
- package/dist/push-validation.d.ts +20 -0
- package/dist/push-validation.d.ts.map +1 -0
- package/dist/push-validation.js +70 -0
- package/dist/push-validation.js.map +1 -0
- package/dist/redact.d.ts +9 -0
- package/dist/redact.d.ts.map +1 -0
- package/dist/redact.js +22 -0
- package/dist/redact.js.map +1 -0
- package/dist/review-command.d.ts +44 -0
- package/dist/review-command.d.ts.map +1 -0
- package/dist/review-command.js +348 -0
- package/dist/review-command.js.map +1 -0
- package/package.json +56 -3
- package/templates/c4/diagrams/views.meltemi +16 -0
- package/templates/c4/models/system.meltemi +23 -0
- package/templates/ci/github.yml +96 -0
- package/templates/ci/gitlab.yml +53 -0
- package/templates/flow/diagrams/overview.meltemi +15 -0
- package/templates/free/diagrams/slide.meltemi +39 -0
- package/templates/purchase/diagrams/architecture.meltemi +36 -0
- package/templates/purchase/diagrams/executive.meltemi +52 -0
- package/templates/purchase/diagrams/payment.meltemi +42 -0
- package/templates/purchase/models/commerce.meltemi +30 -0
- package/templates/purchase/stories/purchase.meltemi +38 -0
- package/templates/sequence/diagrams/sign-in.meltemi +36 -0
|
@@ -0,0 +1,311 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `publish` (`cli-cloud`, "Publish"): the catalog of the folder's project as a web page of the cloud
|
|
3
|
+
* service.
|
|
4
|
+
*
|
|
5
|
+
* The order, and why:
|
|
6
|
+
*
|
|
7
|
+
* 1. The project and the session: the folder's manifest names the cloud project (`cloud.project`,
|
|
8
|
+
* or `--project`), the host is `--host`, `MELTEMI_HOST` or the default, and `cloud.host-mismatch`
|
|
9
|
+
* and "not signed in" fail before any request.
|
|
10
|
+
* 2. `--unpublish` ends here: one `DELETE`, nothing is built or pushed.
|
|
11
|
+
* 3. `GET …/publish` first: it is the owner's, so a project the person may not publish (`access.denied`,
|
|
12
|
+
* `project.not-found`) fails before the catalog is built, and it says whether a handle is chosen.
|
|
13
|
+
* 4. The catalog is built in memory, exactly as `build catalog` builds it (same generator, same
|
|
14
|
+
* defaults, the `--lang` language), before anything is sent: a catalog with nothing in it, with
|
|
15
|
+
* errors under `--strict`, or too large for a page, fails with nothing pushed and nothing
|
|
16
|
+
* published.
|
|
17
|
+
* 5. The handle: the first publication needs one (`--handle`, or asked on a terminal). With
|
|
18
|
+
* `--handle` the handle is set first (`PATCH /api/account`); the service lets a token do that; a
|
|
19
|
+
* service that answers `403 request.forbidden` instead makes it `publish.handle-in-page`.
|
|
20
|
+
* 6. The folder is pushed when it differs from the cloud project: the command of `push`, with its
|
|
21
|
+
* comparison and its question (`--force` answers it, as in CI), so what is published is what the
|
|
22
|
+
* cloud holds.
|
|
23
|
+
* 7. `PUT …/publish`, and the public address printed on a line of its own.
|
|
24
|
+
*/
|
|
25
|
+
import { statSync } from "node:fs";
|
|
26
|
+
import { basename, resolve } from "node:path";
|
|
27
|
+
import { parseArgs } from "node:util";
|
|
28
|
+
import { catalogScript } from "@meltemi/diagrams-catalog/node";
|
|
29
|
+
import { ProjectSessions, buildCatalog } from "@meltemi/diagrams-editor/node";
|
|
30
|
+
import { sortDiagnostics } from "@meltemi/diagrams-language";
|
|
31
|
+
import { BRAND_NAME } from "@meltemi/diagrams-layout";
|
|
32
|
+
import { CLOUD_LIMITS, toProjectPath } from "@meltemi/diagrams-project";
|
|
33
|
+
import { formatSize } from "./build-command.js";
|
|
34
|
+
import { openCloud, usage, usageError, } from "./cloud-context.js";
|
|
35
|
+
import { CloudError, EXIT_OK, EXIT_USAGE, isCloudError } from "./cloud-errors.js";
|
|
36
|
+
import { messages } from "./messages/index.js";
|
|
37
|
+
import { withDetails } from "./project-errors.js";
|
|
38
|
+
import { pushCommand } from "./push-command.js";
|
|
39
|
+
/** The handle and the page name, as the service accepts them. */
|
|
40
|
+
const HANDLE = /^[a-z0-9-]{3,30}$/;
|
|
41
|
+
const SLUG = /^[a-z0-9-]{1,60}$/;
|
|
42
|
+
/** The most a publish request may weigh as JSON: the service's own cap. */
|
|
43
|
+
const REQUEST_BYTES = 24 * 1024 * 1024;
|
|
44
|
+
/**
|
|
45
|
+
* The size that makes a page too large for the service, or undefined when it fits: the HTML over
|
|
46
|
+
* 16 MiB of UTF-8, or the request that carries it over 24 MiB (JSON escapes quotes and the like, so
|
|
47
|
+
* a page below the first limit can still be above the second). Checked here so that nothing is sent.
|
|
48
|
+
*/
|
|
49
|
+
export function oversize(slug, html) {
|
|
50
|
+
const htmlBytes = Buffer.byteLength(html);
|
|
51
|
+
if (htmlBytes > CLOUD_LIMITS.publishBytes)
|
|
52
|
+
return htmlBytes;
|
|
53
|
+
// A request cannot be smaller than its page: only a large page needs the exact count.
|
|
54
|
+
if (htmlBytes <= REQUEST_BYTES / 6)
|
|
55
|
+
return undefined;
|
|
56
|
+
const requestBytes = Buffer.byteLength(JSON.stringify({ slug, html }));
|
|
57
|
+
return requestBytes > REQUEST_BYTES ? requestBytes : undefined;
|
|
58
|
+
}
|
|
59
|
+
/** How many times a handle typed on a terminal may be wrong before the command gives up. */
|
|
60
|
+
const HANDLE_ATTEMPTS = 3;
|
|
61
|
+
/** A page name made of a project's name: lower-case ASCII letters and digits with single hyphens. */
|
|
62
|
+
export function slugOf(text) {
|
|
63
|
+
const slug = text
|
|
64
|
+
.normalize("NFKD")
|
|
65
|
+
.replace(/[̀-ͯ]/g, "")
|
|
66
|
+
.toLowerCase()
|
|
67
|
+
.replace(/[^a-z0-9]+/g, "-")
|
|
68
|
+
.replace(/^-+/, "")
|
|
69
|
+
.slice(0, 60)
|
|
70
|
+
.replace(/-+$/, "");
|
|
71
|
+
return slug === "" ? "diagrams" : slug;
|
|
72
|
+
}
|
|
73
|
+
function parse(context, args, help) {
|
|
74
|
+
let parsed;
|
|
75
|
+
try {
|
|
76
|
+
parsed = parseArgs({
|
|
77
|
+
args: [...args],
|
|
78
|
+
options: {
|
|
79
|
+
slug: { type: "string" },
|
|
80
|
+
handle: { type: "string" },
|
|
81
|
+
unpublish: { type: "boolean", default: false },
|
|
82
|
+
project: { type: "string" },
|
|
83
|
+
host: { type: "string" },
|
|
84
|
+
force: { type: "boolean", default: false },
|
|
85
|
+
strict: { type: "boolean", default: false },
|
|
86
|
+
help: { type: "boolean", short: "h", default: false },
|
|
87
|
+
},
|
|
88
|
+
allowPositionals: true,
|
|
89
|
+
strict: true,
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
catch (error) {
|
|
93
|
+
return usageError(context, error, help);
|
|
94
|
+
}
|
|
95
|
+
const values = parsed.values;
|
|
96
|
+
if (values.help) {
|
|
97
|
+
context.stdout(help);
|
|
98
|
+
return EXIT_OK;
|
|
99
|
+
}
|
|
100
|
+
const t = messages();
|
|
101
|
+
if (parsed.positionals.length > 1)
|
|
102
|
+
return usage(context, t.common.oneProject, help);
|
|
103
|
+
if (values.slug !== undefined && !SLUG.test(values.slug)) {
|
|
104
|
+
return usage(context, t.cloud.publish.slugInvalid(values.slug), help);
|
|
105
|
+
}
|
|
106
|
+
if (values.handle !== undefined && !HANDLE.test(values.handle)) {
|
|
107
|
+
return usage(context, t.cloud.publish.handleOption(values.handle), help);
|
|
108
|
+
}
|
|
109
|
+
if (values.unpublish) {
|
|
110
|
+
for (const [name, given] of [
|
|
111
|
+
["--handle", values.handle !== undefined],
|
|
112
|
+
["--force", values.force],
|
|
113
|
+
["--strict", values.strict],
|
|
114
|
+
]) {
|
|
115
|
+
if (given)
|
|
116
|
+
return usage(context, t.common.cannotCombine("--unpublish", name), help);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
return {
|
|
120
|
+
folder: parsed.positionals[0] ?? ".",
|
|
121
|
+
slug: values.slug,
|
|
122
|
+
handle: values.handle,
|
|
123
|
+
unpublish: values.unpublish === true,
|
|
124
|
+
project: values.project,
|
|
125
|
+
host: values.host,
|
|
126
|
+
force: values.force === true,
|
|
127
|
+
strict: values.strict === true,
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
// ----------------------------------------------------------------------------------- the build
|
|
131
|
+
/** Prints diagnostics as `validate` and `build catalog` print them. */
|
|
132
|
+
function printDiagnostics(context, path, diagnostics) {
|
|
133
|
+
for (const d of diagnostics) {
|
|
134
|
+
context.stdout(`${path}:${d.range.start.line}:${d.range.start.column}: ${d.severity} ${d.code} ${d.message}\n`);
|
|
135
|
+
for (const hint of d.hints ?? []) {
|
|
136
|
+
context.stdout(` ${messages().common.hint}: ${hint}\n`);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
/** The diagnostics of `project.json`, the plugins and the assets, by file: what `build catalog` shows first. */
|
|
141
|
+
function projectDiagnostics(workspace) {
|
|
142
|
+
const manifest = workspace.manifest();
|
|
143
|
+
const byPath = new Map();
|
|
144
|
+
if (manifest.diagnostics.length > 0)
|
|
145
|
+
byPath.set(manifest.path, [...manifest.diagnostics]);
|
|
146
|
+
for (const [path, list] of [
|
|
147
|
+
...workspace.limits(),
|
|
148
|
+
...workspace.plugins().diagnostics,
|
|
149
|
+
...workspace.assets().diagnostics,
|
|
150
|
+
]) {
|
|
151
|
+
byPath.set(path, [...(byPath.get(path) ?? []), ...list]);
|
|
152
|
+
}
|
|
153
|
+
return new Map([...byPath].map(([path, list]) => [path, sortDiagnostics(list)]));
|
|
154
|
+
}
|
|
155
|
+
/** The catalog of a folder's project, exactly as `build catalog` builds it, and whether it has errors. */
|
|
156
|
+
function buildPublication(context, projects, workspace) {
|
|
157
|
+
let errors = false;
|
|
158
|
+
for (const [path, diagnostics] of projectDiagnostics(workspace)) {
|
|
159
|
+
printDiagnostics(context, context.display(path), diagnostics);
|
|
160
|
+
errors ||= diagnostics.some((d) => d.severity === "error");
|
|
161
|
+
}
|
|
162
|
+
const build = buildCatalog(workspace, projects, {
|
|
163
|
+
generator: `${BRAND_NAME} ${context.toolVersion}`,
|
|
164
|
+
script: catalogScript,
|
|
165
|
+
});
|
|
166
|
+
for (const [file, diagnostics] of [...build.diagnostics].sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0)) {
|
|
167
|
+
printDiagnostics(context, context.display(file), diagnostics);
|
|
168
|
+
errors ||= diagnostics.some((d) => d.severity === "error");
|
|
169
|
+
}
|
|
170
|
+
const t = messages().build;
|
|
171
|
+
for (const entry of build.skipped) {
|
|
172
|
+
context.stderr(`${context.name} ${context.command}: ${t.skipped(`${context.display(entry.path)}#${entry.id}`, entry.reason)}\n`);
|
|
173
|
+
}
|
|
174
|
+
for (const warning of build.warnings) {
|
|
175
|
+
context.stderr(`${context.name} ${context.command}: ${t.warning(warning)}\n`);
|
|
176
|
+
}
|
|
177
|
+
return { build, errors };
|
|
178
|
+
}
|
|
179
|
+
// ---------------------------------------------------------------------------------- the handle
|
|
180
|
+
/**
|
|
181
|
+
* The handle to set before publishing, or undefined when the account's own will do: the one given
|
|
182
|
+
* with `--handle` when it is not the account's already, else (the account has none) the one asked
|
|
183
|
+
* for on a terminal; without a terminal, `handle.required`.
|
|
184
|
+
*/
|
|
185
|
+
async function handleToSet(context, current, given) {
|
|
186
|
+
if (given !== undefined)
|
|
187
|
+
return given === current ? undefined : given;
|
|
188
|
+
if (current !== null)
|
|
189
|
+
return undefined;
|
|
190
|
+
const required = () => new CloudError("handle.required", "Choose a handle before the first publication.");
|
|
191
|
+
const t = messages().cloud;
|
|
192
|
+
if (context.ask === undefined)
|
|
193
|
+
throw required();
|
|
194
|
+
for (let attempt = 0; attempt < HANDLE_ATTEMPTS; attempt++) {
|
|
195
|
+
const answer = (await context.ask(t.publish.askHandle)).trim();
|
|
196
|
+
if (answer === "")
|
|
197
|
+
throw required();
|
|
198
|
+
if (HANDLE.test(answer))
|
|
199
|
+
return answer;
|
|
200
|
+
context.stderr(`${context.name} ${context.command}: ${t.errors.handleInvalid(answer)}\n`);
|
|
201
|
+
}
|
|
202
|
+
throw required();
|
|
203
|
+
}
|
|
204
|
+
// ---------------------------------------------------------------------------------- the command
|
|
205
|
+
/**
|
|
206
|
+
* The `publish` command, with the step that pushes the folder given: `push`'s own command, which
|
|
207
|
+
* compares the folder with the cloud project and asks (or is forced), and answers its exit code.
|
|
208
|
+
*/
|
|
209
|
+
export function createPublishCommand(push) {
|
|
210
|
+
return async (args, context) => {
|
|
211
|
+
const help = messages().help.publish;
|
|
212
|
+
const options = parse(context, args, help);
|
|
213
|
+
if (typeof options === "number")
|
|
214
|
+
return options;
|
|
215
|
+
const t = messages().cloud;
|
|
216
|
+
const folder = toProjectPath(resolve(context.cwd, options.folder));
|
|
217
|
+
try {
|
|
218
|
+
if (!statSync(folder).isDirectory())
|
|
219
|
+
throw new Error("not a folder");
|
|
220
|
+
}
|
|
221
|
+
catch {
|
|
222
|
+
context.stderr(`${context.name} ${context.command}: ${messages().common.pathNotFound(options.folder)}\n`);
|
|
223
|
+
return EXIT_USAGE;
|
|
224
|
+
}
|
|
225
|
+
const projects = new ProjectSessions({
|
|
226
|
+
toolVersion: context.toolVersion,
|
|
227
|
+
...(context.locale ? { locale: context.locale } : {}),
|
|
228
|
+
});
|
|
229
|
+
const workspace = projects.forFolder(folder);
|
|
230
|
+
const manifest = workspace.manifest().manifest;
|
|
231
|
+
const project = options.project ?? manifest?.cloud?.project;
|
|
232
|
+
if (project === undefined) {
|
|
233
|
+
throw new CloudError("cloud.no-project", "The folder names no cloud project.");
|
|
234
|
+
}
|
|
235
|
+
const { host, client } = openCloud(context, {
|
|
236
|
+
host: options.host,
|
|
237
|
+
manifestHost: manifest?.cloud?.host,
|
|
238
|
+
});
|
|
239
|
+
context.used.project = project;
|
|
240
|
+
const slug = options.slug ?? slugOf(manifest?.name ?? basename(workspace.root));
|
|
241
|
+
if (options.unpublish) {
|
|
242
|
+
try {
|
|
243
|
+
await client.unpublish(project, slug);
|
|
244
|
+
}
|
|
245
|
+
catch (error) {
|
|
246
|
+
throw isCloudError(error, "not-found")
|
|
247
|
+
? new CloudError("publish.not-published", error.message, { details: { slug } })
|
|
248
|
+
: error;
|
|
249
|
+
}
|
|
250
|
+
context.stdout(`${t.publish.unpublished(slug)}\n`);
|
|
251
|
+
return EXIT_OK;
|
|
252
|
+
}
|
|
253
|
+
// The owner's route first: nothing is built for a project the person may not publish.
|
|
254
|
+
const state = await client.publication(project);
|
|
255
|
+
const { build, errors } = buildPublication(context, projects, workspace);
|
|
256
|
+
if (build.data.entries.length === 0) {
|
|
257
|
+
throw new CloudError("publish.empty", "The project has nothing to publish.");
|
|
258
|
+
}
|
|
259
|
+
if (options.strict && (errors || build.skipped.length > 0)) {
|
|
260
|
+
throw new CloudError("publish.errors", "The project has errors.");
|
|
261
|
+
}
|
|
262
|
+
const htmlBytes = Buffer.byteLength(build.html);
|
|
263
|
+
const tooLarge = oversize(slug, build.html);
|
|
264
|
+
if (tooLarge !== undefined) {
|
|
265
|
+
throw new CloudError("publish.too-large", "The catalog is larger than a page may be.", {
|
|
266
|
+
details: { bytes: tooLarge },
|
|
267
|
+
});
|
|
268
|
+
}
|
|
269
|
+
const handle = await handleToSet(context, state.handle, options.handle);
|
|
270
|
+
if (handle !== undefined) {
|
|
271
|
+
try {
|
|
272
|
+
await client.setHandle(handle);
|
|
273
|
+
}
|
|
274
|
+
catch (error) {
|
|
275
|
+
throw withDetails(isCloudError(error, "request.forbidden")
|
|
276
|
+
? new CloudError("publish.handle-in-page", error.message, { status: error.status })
|
|
277
|
+
: error, { handle, current: state.handle });
|
|
278
|
+
}
|
|
279
|
+
context.stdout(`${t.publish.handleSet(handle)}\n`);
|
|
280
|
+
}
|
|
281
|
+
const pushed = await push([
|
|
282
|
+
options.folder,
|
|
283
|
+
"--project",
|
|
284
|
+
project,
|
|
285
|
+
"--host",
|
|
286
|
+
host,
|
|
287
|
+
...(options.force ? ["--force"] : []),
|
|
288
|
+
...(options.strict ? ["--strict"] : []),
|
|
289
|
+
], context);
|
|
290
|
+
if (pushed !== EXIT_OK)
|
|
291
|
+
return pushed;
|
|
292
|
+
let published;
|
|
293
|
+
try {
|
|
294
|
+
published = await client.publish(project, { slug, html: build.html });
|
|
295
|
+
}
|
|
296
|
+
catch (error) {
|
|
297
|
+
throw withDetails(error, { slug, bytes: htmlBytes });
|
|
298
|
+
}
|
|
299
|
+
const diagrams = build.data.entries.filter((e) => e.kind === "diagram").length;
|
|
300
|
+
const b = messages().build;
|
|
301
|
+
context.stdout(`${t.publish.published([
|
|
302
|
+
b.diagrams(diagrams),
|
|
303
|
+
b.stories(build.data.entries.length - diagrams),
|
|
304
|
+
...(build.data.entities.length > 0 ? [b.entities(build.data.entities.length)] : []),
|
|
305
|
+
], formatSize(htmlBytes))}\n${published.url}\n`);
|
|
306
|
+
return EXIT_OK;
|
|
307
|
+
};
|
|
308
|
+
}
|
|
309
|
+
/** `publish`, pushing the folder with `push`'s own command. */
|
|
310
|
+
export const publishCommand = createPublishCommand(pushCommand);
|
|
311
|
+
//# sourceMappingURL=publish-command.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"publish-command.js","sourceRoot":"","sources":["../src/publish-command.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `mdg pull [<id>] [folder] [--version <n>] [--force]` (`cli-cloud`, "Pull"): writes a cloud
|
|
3
|
+
* project's files into a folder, and never writes anything it has not checked first.
|
|
4
|
+
*
|
|
5
|
+
* Every path the cloud answers is checked before the first byte is written (`cloud-paths.ts`): it
|
|
6
|
+
* must be in the canonical form the cloud keeps (`isCleanPath`, so no `..`, no absolute path, no
|
|
7
|
+
* backslash, nothing hidden such as `.git/hooks/post-checkout`), a name Windows could keep, not
|
|
8
|
+
* differ from another path only in letter case, and resolve inside the folder; and it must not go
|
|
9
|
+
* through a link in the folder (a link is never followed out of it). One that fails refuses the
|
|
10
|
+
* whole pull with `pull.unsafe-path` naming it, and nothing is written, not even the folder.
|
|
11
|
+
*
|
|
12
|
+
* Then, per file: absent in the folder, it is written (images and fonts are fetched by hash and
|
|
13
|
+
* checked against it); identical, it is left alone; existing and different, it is listed and kept
|
|
14
|
+
* unless `--force`; a folder where a file belongs is kept too. Files only in the folder stay. The
|
|
15
|
+
* losers of a name collision (`<path>~<id>`) are skipped with a note. `project.json` gains
|
|
16
|
+
* `cloud` when it has none, through `setJsonObjectField`, and nothing else of it changes. The exit
|
|
17
|
+
* code is 1 when a file was kept.
|
|
18
|
+
*/
|
|
19
|
+
import { type CloudCommand } from "./cloud-context.js";
|
|
20
|
+
export declare const pullCommand: CloudCommand;
|
|
21
|
+
//# sourceMappingURL=pull-command.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pull-command.d.ts","sourceRoot":"","sources":["../src/pull-command.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAyBH,OAAO,EAIL,KAAK,YAAY,EAElB,MAAM,oBAAoB,CAAC;AA6F5B,eAAO,MAAM,WAAW,EAAE,YA+GzB,CAAC"}
|
|
@@ -0,0 +1,300 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `mdg pull [<id>] [folder] [--version <n>] [--force]` (`cli-cloud`, "Pull"): writes a cloud
|
|
3
|
+
* project's files into a folder, and never writes anything it has not checked first.
|
|
4
|
+
*
|
|
5
|
+
* Every path the cloud answers is checked before the first byte is written (`cloud-paths.ts`): it
|
|
6
|
+
* must be in the canonical form the cloud keeps (`isCleanPath`, so no `..`, no absolute path, no
|
|
7
|
+
* backslash, nothing hidden such as `.git/hooks/post-checkout`), a name Windows could keep, not
|
|
8
|
+
* differ from another path only in letter case, and resolve inside the folder; and it must not go
|
|
9
|
+
* through a link in the folder (a link is never followed out of it). One that fails refuses the
|
|
10
|
+
* whole pull with `pull.unsafe-path` naming it, and nothing is written, not even the folder.
|
|
11
|
+
*
|
|
12
|
+
* Then, per file: absent in the folder, it is written (images and fonts are fetched by hash and
|
|
13
|
+
* checked against it); identical, it is left alone; existing and different, it is listed and kept
|
|
14
|
+
* unless `--force`; a folder where a file belongs is kept too. Files only in the folder stay. The
|
|
15
|
+
* losers of a name collision (`<path>~<id>`) are skipped with a note. `project.json` gains
|
|
16
|
+
* `cloud` when it has none, through `setJsonObjectField`, and nothing else of it changes. The exit
|
|
17
|
+
* code is 1 when a file was kept.
|
|
18
|
+
*/
|
|
19
|
+
import { randomBytes } from "node:crypto";
|
|
20
|
+
import { existsSync, lstatSync, mkdirSync, readFileSync, realpathSync, renameSync, rmSync, statSync, writeFileSync, } from "node:fs";
|
|
21
|
+
import { dirname, join, resolve } from "node:path";
|
|
22
|
+
import { parseArgs } from "node:util";
|
|
23
|
+
import { CLOUD_LIMITS, CLOUD_PROJECT_ID, MANIFEST_NAME, readManifest, toProjectPath, } from "@meltemi/diagrams-project";
|
|
24
|
+
import { createNodeHost } from "@meltemi/diagrams-project/node";
|
|
25
|
+
import { sha256Hex } from "./cloud-client.js";
|
|
26
|
+
import { openCloud, usage, usageError, } from "./cloud-context.js";
|
|
27
|
+
import { CloudError, EXIT_FAILED, EXIT_OK } from "./cloud-errors.js";
|
|
28
|
+
import { withCloudField } from "./cloud-manifest.js";
|
|
29
|
+
import { collisionLosers, insideFolder, pathProblem, printablePath, setProblems, } from "./cloud-paths.js";
|
|
30
|
+
import { messages } from "./messages/index.js";
|
|
31
|
+
import { withDetails } from "./project-errors.js";
|
|
32
|
+
/** Looks at the folder's tree for a path, one name at a time, never following a link. */
|
|
33
|
+
function inspect(root, path) {
|
|
34
|
+
const names = path.split("/");
|
|
35
|
+
let current = root;
|
|
36
|
+
for (let i = 0; i < names.length; i++) {
|
|
37
|
+
current = join(current, names[i]);
|
|
38
|
+
let stats;
|
|
39
|
+
try {
|
|
40
|
+
stats = lstatSync(current);
|
|
41
|
+
}
|
|
42
|
+
catch (error) {
|
|
43
|
+
const code = error.code;
|
|
44
|
+
return code === "ENOENT" || code === "ENOTDIR" ? { state: "free" } : { state: "blocked" };
|
|
45
|
+
}
|
|
46
|
+
if (stats.isSymbolicLink())
|
|
47
|
+
return { state: "link" };
|
|
48
|
+
if (i < names.length - 1) {
|
|
49
|
+
if (!stats.isDirectory())
|
|
50
|
+
return { state: "blocked" };
|
|
51
|
+
}
|
|
52
|
+
else if (stats.isFile()) {
|
|
53
|
+
return { state: "file", file: current, size: stats.size };
|
|
54
|
+
}
|
|
55
|
+
else {
|
|
56
|
+
return { state: "blocked" };
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
return { state: "free" };
|
|
60
|
+
}
|
|
61
|
+
/** Writes a file through a temporary file in its folder and a rename, so a link there is replaced, not followed. */
|
|
62
|
+
function writeFile(target, data) {
|
|
63
|
+
mkdirSync(dirname(target), { recursive: true });
|
|
64
|
+
const temporary = `${target}.${process.pid}.${randomBytes(4).toString("hex")}.tmp`;
|
|
65
|
+
try {
|
|
66
|
+
writeFileSync(temporary, data, { flag: "wx" });
|
|
67
|
+
renameSync(temporary, target);
|
|
68
|
+
}
|
|
69
|
+
catch (error) {
|
|
70
|
+
rmSync(temporary, { force: true });
|
|
71
|
+
throw error;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
/** Whether the file in the folder holds what the cloud holds. */
|
|
75
|
+
function sameContent(slot, kind, content) {
|
|
76
|
+
try {
|
|
77
|
+
if (kind === "binary") {
|
|
78
|
+
if (slot.size > CLOUD_LIMITS.binaryBytes)
|
|
79
|
+
return false;
|
|
80
|
+
return sha256Hex(readFileSync(slot.file)) === content;
|
|
81
|
+
}
|
|
82
|
+
// A text is content, byte for byte: the line endings and a byte order mark included.
|
|
83
|
+
return (slot.size === Buffer.byteLength(content) &&
|
|
84
|
+
readFileSync(slot.file).equals(Buffer.from(content, "utf8")));
|
|
85
|
+
}
|
|
86
|
+
catch {
|
|
87
|
+
// A file that cannot be read cannot be said to match: it is kept, or fails to be written.
|
|
88
|
+
return false;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
export const pullCommand = async (args, context) => {
|
|
92
|
+
const help = messages().help.pull;
|
|
93
|
+
const t = messages().cloud.pull;
|
|
94
|
+
let parsed;
|
|
95
|
+
try {
|
|
96
|
+
parsed = parseArgs({
|
|
97
|
+
args: [...args],
|
|
98
|
+
options: {
|
|
99
|
+
host: { type: "string" },
|
|
100
|
+
version: { type: "string" },
|
|
101
|
+
force: { type: "boolean", default: false },
|
|
102
|
+
help: { type: "boolean", short: "h", default: false },
|
|
103
|
+
},
|
|
104
|
+
allowPositionals: true,
|
|
105
|
+
strict: true,
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
catch (error) {
|
|
109
|
+
return usageError(context, error, help);
|
|
110
|
+
}
|
|
111
|
+
const values = parsed.values;
|
|
112
|
+
if (values.help) {
|
|
113
|
+
context.stdout(help);
|
|
114
|
+
return EXIT_OK;
|
|
115
|
+
}
|
|
116
|
+
if (parsed.positionals.length > 2)
|
|
117
|
+
return usage(context, t.tooMany, help);
|
|
118
|
+
let version;
|
|
119
|
+
if (values.version !== undefined) {
|
|
120
|
+
if (!/^[0-9]+$/.test(values.version) || Number(values.version) < 1) {
|
|
121
|
+
return usage(context, t.version(values.version), help);
|
|
122
|
+
}
|
|
123
|
+
version = Number(values.version);
|
|
124
|
+
}
|
|
125
|
+
// One argument is the id when it has the shape of one, else the folder.
|
|
126
|
+
let id;
|
|
127
|
+
let given;
|
|
128
|
+
if (parsed.positionals.length === 2)
|
|
129
|
+
[id, given] = parsed.positionals;
|
|
130
|
+
else if (parsed.positionals[0] !== undefined) {
|
|
131
|
+
if (CLOUD_PROJECT_ID.test(parsed.positionals[0]))
|
|
132
|
+
id = parsed.positionals[0];
|
|
133
|
+
else
|
|
134
|
+
given = parsed.positionals[0];
|
|
135
|
+
}
|
|
136
|
+
const folder = resolve(context.cwd, given ?? ".");
|
|
137
|
+
if (existsSync(folder) && !statSync(folder).isDirectory()) {
|
|
138
|
+
throw new CloudError("pull.not-a-folder", `${folder} exists and is not a folder.`, {
|
|
139
|
+
details: { path: context.display(folder) },
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
const manifest = readManifest(createNodeHost(), toProjectPath(folder), {
|
|
143
|
+
toolVersion: context.toolVersion,
|
|
144
|
+
}).manifest;
|
|
145
|
+
const linked = manifest?.cloud ?? null;
|
|
146
|
+
const { host, client } = openCloud(context, { host: values.host, manifestHost: linked?.host });
|
|
147
|
+
const project = id ?? linked?.project;
|
|
148
|
+
if (project === undefined) {
|
|
149
|
+
throw new CloudError("pull.no-project", "No cloud project is named.");
|
|
150
|
+
}
|
|
151
|
+
context.used.project = project;
|
|
152
|
+
if (id !== undefined && linked !== null && linked.project !== id) {
|
|
153
|
+
throw new CloudError("pull.other-project", "The folder is another cloud project's.", {
|
|
154
|
+
details: { project: id, linked: linked.project },
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
const answer = await read(client, project, version);
|
|
158
|
+
const everything = [...Object.keys(answer.files), ...Object.keys(answer.binaries)];
|
|
159
|
+
const losers = collisionLosers(everything, answer.collisions);
|
|
160
|
+
const entries = plan(folder, answer, everything.filter((path) => !losers.has(path)), values.force);
|
|
161
|
+
// Everything is checked; from here on the folder is written.
|
|
162
|
+
const lines = [];
|
|
163
|
+
let written = 0;
|
|
164
|
+
let identical = 0;
|
|
165
|
+
const kept = [];
|
|
166
|
+
for (const entry of entries) {
|
|
167
|
+
if (entry.action === "same") {
|
|
168
|
+
identical++;
|
|
169
|
+
}
|
|
170
|
+
else if (entry.action === "write") {
|
|
171
|
+
try {
|
|
172
|
+
const data = entry.kind === "text" ? entry.content : await client.blob(project, entry.content);
|
|
173
|
+
writeFile(entry.target, data);
|
|
174
|
+
}
|
|
175
|
+
catch (error) {
|
|
176
|
+
if (error instanceof CloudError)
|
|
177
|
+
throw withDetails(error, { project });
|
|
178
|
+
throw new CloudError("pull.write", error.message, {
|
|
179
|
+
details: { path: entry.path, reason: error.message },
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
written++;
|
|
183
|
+
lines.push(t.wrote(entry.path));
|
|
184
|
+
}
|
|
185
|
+
else {
|
|
186
|
+
kept.push(entry.action === "blocked" ? t.inTheWay(entry.path) : t.kept(entry.path));
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
for (const path of [...losers].filter((p) => everything.includes(p)).sort()) {
|
|
190
|
+
lines.push(t.loser(printablePath(path)));
|
|
191
|
+
}
|
|
192
|
+
for (const line of lines)
|
|
193
|
+
context.stdout(`${line}\n`);
|
|
194
|
+
for (const line of kept)
|
|
195
|
+
context.stdout(`${line}\n`);
|
|
196
|
+
if (kept.length > 0 && !values.force)
|
|
197
|
+
context.stdout(`${t.keptHint}\n`);
|
|
198
|
+
const failure = recordProject(context, folder, host, project);
|
|
199
|
+
context.stdout(`${t.pulled(project, version ?? null, context.display(folder), written, identical, kept.length)}\n`);
|
|
200
|
+
if (failure !== undefined)
|
|
201
|
+
throw failure;
|
|
202
|
+
return kept.length > 0 ? EXIT_FAILED : EXIT_OK;
|
|
203
|
+
};
|
|
204
|
+
/** The project's files, or a version's, as the service answers them. */
|
|
205
|
+
async function read(client, project, version) {
|
|
206
|
+
try {
|
|
207
|
+
if (version === undefined)
|
|
208
|
+
return await client.files(project);
|
|
209
|
+
return {
|
|
210
|
+
...(await client.readVersion(project, version)),
|
|
211
|
+
collisions: [],
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
catch (error) {
|
|
215
|
+
throw withDetails(error, { project, ...(version === undefined ? {} : { version }) });
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
/**
|
|
219
|
+
* What to do with each of the cloud's paths in a folder, after every one has been checked. Throws
|
|
220
|
+
* `pull.unsafe-path` (nothing written) for the first path that is not safe.
|
|
221
|
+
*/
|
|
222
|
+
function plan(folder, answer, paths, force) {
|
|
223
|
+
const root = existsSync(folder) ? realpathSync(folder) : folder;
|
|
224
|
+
const unsafe = [];
|
|
225
|
+
const sorted = [...paths].sort((a, b) => (a < b ? -1 : a > b ? 1 : 0));
|
|
226
|
+
const named = sorted.filter((path) => pathProblem(path) === undefined);
|
|
227
|
+
const together = setProblems(named);
|
|
228
|
+
const entries = [];
|
|
229
|
+
for (const path of sorted) {
|
|
230
|
+
const target = pathProblem(path) === undefined ? insideFolder(root, path) : undefined;
|
|
231
|
+
if (target === undefined || together.has(path)) {
|
|
232
|
+
unsafe.push(path);
|
|
233
|
+
continue;
|
|
234
|
+
}
|
|
235
|
+
const text = Object.hasOwn(answer.files, path);
|
|
236
|
+
const kind = text ? "text" : "binary";
|
|
237
|
+
const content = text ? answer.files[path] : answer.binaries[path];
|
|
238
|
+
const slot = inspect(root, path);
|
|
239
|
+
if (slot.state === "link")
|
|
240
|
+
unsafe.push(path);
|
|
241
|
+
else if (slot.state === "blocked")
|
|
242
|
+
entries.push({ path, target, kind, content, action: "blocked" });
|
|
243
|
+
else if (slot.state === "free")
|
|
244
|
+
entries.push({ path, target, kind, content, action: "write" });
|
|
245
|
+
else if (sameContent(slot, kind, content)) {
|
|
246
|
+
entries.push({ path, target, kind, content, action: "same" });
|
|
247
|
+
}
|
|
248
|
+
else
|
|
249
|
+
entries.push({ path, target, kind, content, action: force ? "write" : "keep" });
|
|
250
|
+
}
|
|
251
|
+
if (unsafe.length > 0) {
|
|
252
|
+
// The path is the cloud's to name and the terminal is the person's: it is printed escaped.
|
|
253
|
+
const shown = printablePath(unsafe[0]);
|
|
254
|
+
throw new CloudError("pull.unsafe-path", `The cloud project holds ${shown}, which is not a safe path inside the folder.`, { details: { path: shown } });
|
|
255
|
+
}
|
|
256
|
+
return entries;
|
|
257
|
+
}
|
|
258
|
+
/**
|
|
259
|
+
* Adds `cloud` to the folder's `project.json` when it has none (or one that is not valid), through
|
|
260
|
+
* `setJsonObjectField`: the rest of the file is untouched, byte for byte. Answers the failure, when
|
|
261
|
+
* there is one, so that the summary is printed first.
|
|
262
|
+
*/
|
|
263
|
+
function recordProject(context, folder, host, project) {
|
|
264
|
+
const t = messages().cloud.pull;
|
|
265
|
+
const file = join(folder, MANIFEST_NAME);
|
|
266
|
+
if (!existsSync(file)) {
|
|
267
|
+
context.stdout(`${t.noManifest}\n`);
|
|
268
|
+
return undefined;
|
|
269
|
+
}
|
|
270
|
+
const current = readManifest(createNodeHost(), toProjectPath(folder), {
|
|
271
|
+
toolVersion: context.toolVersion,
|
|
272
|
+
}).manifest;
|
|
273
|
+
// A project.json pulled from the cloud may name ANOTHER project: a copy of a project (the page's
|
|
274
|
+
// Duplicate) keeps its files verbatim. The folder belongs to the project it was pulled from, so
|
|
275
|
+
// the setting is rewritten, or the next push would overwrite the project it names.
|
|
276
|
+
if (current?.cloud && current.cloud.project === project && current.cloud.host === host) {
|
|
277
|
+
return undefined;
|
|
278
|
+
}
|
|
279
|
+
// A link is never followed (the files of a pull are not written through one either): the setting
|
|
280
|
+
// is not written into whatever a link points at.
|
|
281
|
+
if (lstatSync(file).isSymbolicLink()) {
|
|
282
|
+
return new CloudError("pull.manifest-not-updated", "project.json is a link.", {
|
|
283
|
+
details: { reason: "project.json is a link, and a pull never writes through one" },
|
|
284
|
+
});
|
|
285
|
+
}
|
|
286
|
+
try {
|
|
287
|
+
const text = readFileSync(file, "utf8");
|
|
288
|
+
const edited = withCloudField(text, host, project);
|
|
289
|
+
if (edited !== text)
|
|
290
|
+
writeFileSync(file, edited, "utf8");
|
|
291
|
+
context.stdout(`${t.recorded(context.display(file))}\n`);
|
|
292
|
+
return undefined;
|
|
293
|
+
}
|
|
294
|
+
catch (error) {
|
|
295
|
+
return new CloudError("pull.manifest-not-updated", error.message, {
|
|
296
|
+
details: { reason: error.message },
|
|
297
|
+
});
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
//# sourceMappingURL=pull-command.js.map
|