@adobe/aem-cli 16.19.13 → 16.20.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/CHANGELOG.md +14 -0
- package/package.json +1 -1
- package/src/content/clone.cmd.js +35 -14
- package/src/content/clone.js +15 -0
- package/src/content/content-shared.js +25 -0
- package/src/content/da-api.js +20 -17
- package/src/content/diff.cmd.js +3 -8
- package/src/content/merge.cmd.js +3 -8
- package/src/content/push.cmd.js +15 -20
- package/src/content/status.cmd.js +3 -9
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,17 @@
|
|
|
1
|
+
# [16.20.0](https://github.com/adobe/helix-cli/compare/v16.19.14...v16.20.0) (2026-06-04)
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
### Features
|
|
5
|
+
|
|
6
|
+
* **content:** add --org/--site options to aem content clone ([#2738](https://github.com/adobe/helix-cli/issues/2738)) ([58f5b54](https://github.com/adobe/helix-cli/commit/58f5b54c729990359e11a02f569866083ac1c393)), closes [#2725](https://github.com/adobe/helix-cli/issues/2725)
|
|
7
|
+
|
|
8
|
+
## [16.19.14](https://github.com/adobe/helix-cli/compare/v16.19.13...v16.19.14) (2026-06-04)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
### Bug Fixes
|
|
12
|
+
|
|
13
|
+
* **content:** use org/site terminology and centralize config reading ([#2737](https://github.com/adobe/helix-cli/issues/2737)) ([bb0d357](https://github.com/adobe/helix-cli/commit/bb0d35744b41487245c269de6000a0d9a23d5bdb))
|
|
14
|
+
|
|
1
15
|
## [16.19.13](https://github.com/adobe/helix-cli/compare/v16.19.12...v16.19.13) (2026-06-03)
|
|
2
16
|
|
|
3
17
|
|
package/package.json
CHANGED
package/src/content/clone.cmd.js
CHANGED
|
@@ -67,6 +67,8 @@ export default class CloneCommand {
|
|
|
67
67
|
this._force = false;
|
|
68
68
|
this._assumeYes = false;
|
|
69
69
|
this._rootPath = null;
|
|
70
|
+
this._org = null;
|
|
71
|
+
this._site = null;
|
|
70
72
|
}
|
|
71
73
|
|
|
72
74
|
withDirectory(dir) {
|
|
@@ -94,6 +96,16 @@ export default class CloneCommand {
|
|
|
94
96
|
return this;
|
|
95
97
|
}
|
|
96
98
|
|
|
99
|
+
withOrg(org) {
|
|
100
|
+
this._org = org ? org.trim() : null;
|
|
101
|
+
return this;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
withSite(site) {
|
|
105
|
+
this._site = site ? site.trim() : null;
|
|
106
|
+
return this;
|
|
107
|
+
}
|
|
108
|
+
|
|
97
109
|
async run() {
|
|
98
110
|
const { log } = this;
|
|
99
111
|
|
|
@@ -101,14 +113,23 @@ export default class CloneCommand {
|
|
|
101
113
|
throw new Error('Clone root path was not set (internal error).');
|
|
102
114
|
}
|
|
103
115
|
|
|
104
|
-
// 1. Resolve
|
|
105
|
-
const
|
|
106
|
-
|
|
107
|
-
|
|
116
|
+
// 1. Resolve org/site: explicit --org/--site override, otherwise derive from git remote
|
|
117
|
+
const hasOrg = !!this._org;
|
|
118
|
+
const hasSite = !!this._site;
|
|
119
|
+
let org;
|
|
120
|
+
let site;
|
|
121
|
+
if (hasOrg && hasSite) {
|
|
122
|
+
org = this._org.toLowerCase();
|
|
123
|
+
site = this._site.toLowerCase();
|
|
124
|
+
} else {
|
|
125
|
+
const originUrl = await GitUtils.getOriginURL(this._dir);
|
|
126
|
+
if (!originUrl) {
|
|
127
|
+
throw new Error('No git remote found. Run `aem content clone` inside an AEM project directory.');
|
|
128
|
+
}
|
|
129
|
+
org = originUrl.owner.toLowerCase();
|
|
130
|
+
site = originUrl.repo.toLowerCase();
|
|
108
131
|
}
|
|
109
|
-
|
|
110
|
-
const repo = originUrl.repo.toLowerCase();
|
|
111
|
-
log.info(`Cloning content from da.live: ${owner}/${repo}${this._rootPath === '/' ? '' : ` @ ${this._rootPath}`}`);
|
|
132
|
+
log.info(`Cloning content from da.live: ${org}/${site}${this._rootPath === '/' ? '' : ` @ ${this._rootPath}`}`);
|
|
112
133
|
|
|
113
134
|
// 2. Ensure target path is available (do not create content/ until after file count is known)
|
|
114
135
|
const contentDir = path.resolve(this._dir, CONTENT_DIR);
|
|
@@ -126,7 +147,7 @@ export default class CloneCommand {
|
|
|
126
147
|
const client = new DaClient(token);
|
|
127
148
|
log.info('Fetching file list...');
|
|
128
149
|
const showDiscoveryProgress = process.stdout.isTTY;
|
|
129
|
-
const files = await client.listAll(
|
|
150
|
+
const files = await client.listAll(org, site, this._rootPath, showDiscoveryProgress
|
|
130
151
|
? (n) => {
|
|
131
152
|
process.stdout.write(`\r ${n} file(s) discovered so far...`);
|
|
132
153
|
}
|
|
@@ -148,15 +169,15 @@ export default class CloneCommand {
|
|
|
148
169
|
const downloadResults = await processQueue(
|
|
149
170
|
files,
|
|
150
171
|
async (file) => {
|
|
151
|
-
const prefix = `/${
|
|
172
|
+
const prefix = `/${org}/${site}`;
|
|
152
173
|
if (!file.path.startsWith(prefix)) {
|
|
153
|
-
log.warn(` skip (unexpected path, missing
|
|
174
|
+
log.warn(` skip (unexpected path, missing org/site prefix): ${file.path}`);
|
|
154
175
|
return { status: 404 };
|
|
155
176
|
}
|
|
156
177
|
const daPath = file.path.slice(prefix.length) || '/';
|
|
157
178
|
const localPath = path.join(contentDir, ...daPath.split('/').filter(Boolean));
|
|
158
179
|
try {
|
|
159
|
-
const res = await client.getSource(
|
|
180
|
+
const res = await client.getSource(org, site, daPath);
|
|
160
181
|
if (!res) {
|
|
161
182
|
log.warn(` skip (not found): ${daPath}`);
|
|
162
183
|
return { status: 404 };
|
|
@@ -195,7 +216,7 @@ export default class CloneCommand {
|
|
|
195
216
|
await git.commit({
|
|
196
217
|
fs,
|
|
197
218
|
dir: contentDir,
|
|
198
|
-
message: `clone: ${
|
|
219
|
+
message: `clone: ${org}/${site}${this._rootPath === '/' ? '' : ` (${this._rootPath})`}`,
|
|
199
220
|
author: GIT_AUTHOR,
|
|
200
221
|
});
|
|
201
222
|
const headOid = await git.resolveRef({ fs, dir: contentDir, ref: 'HEAD' });
|
|
@@ -203,8 +224,8 @@ export default class CloneCommand {
|
|
|
203
224
|
|
|
204
225
|
// 8. Write config (not tracked by git)
|
|
205
226
|
await fse.writeJson(path.join(contentDir, CONFIG_FILE), {
|
|
206
|
-
|
|
207
|
-
|
|
227
|
+
org,
|
|
228
|
+
site,
|
|
208
229
|
rootPath: this._rootPath,
|
|
209
230
|
}, { spaces: 2 });
|
|
210
231
|
|
package/src/content/clone.js
CHANGED
|
@@ -22,6 +22,14 @@ export default function clone() {
|
|
|
22
22
|
description: 'Clone da.live content locally into content/',
|
|
23
23
|
builder: (yargs) => {
|
|
24
24
|
yargs
|
|
25
|
+
.option('org', {
|
|
26
|
+
describe: 'da.live org to clone from. Overrides the org derived from the git remote.',
|
|
27
|
+
type: 'string',
|
|
28
|
+
})
|
|
29
|
+
.option('site', {
|
|
30
|
+
describe: 'da.live site to clone from. Overrides the site derived from the git remote.',
|
|
31
|
+
type: 'string',
|
|
32
|
+
})
|
|
25
33
|
.option('path', {
|
|
26
34
|
describe: 'da.live folder to clone (e.g. /ca/fr_ca). Omit only when using --all.',
|
|
27
35
|
type: 'string',
|
|
@@ -47,6 +55,11 @@ export default function clone() {
|
|
|
47
55
|
default: false,
|
|
48
56
|
})
|
|
49
57
|
.check((argv) => {
|
|
58
|
+
const org = (argv.org || '').trim();
|
|
59
|
+
const site = (argv.site || '').trim();
|
|
60
|
+
if ((org && !site) || (!org && site)) {
|
|
61
|
+
return '--org and --site must be used together.';
|
|
62
|
+
}
|
|
50
63
|
if (argv.all && argv.path !== undefined && argv.path !== '') {
|
|
51
64
|
return 'Do not use --path together with --all.';
|
|
52
65
|
}
|
|
@@ -67,6 +80,8 @@ export default function clone() {
|
|
|
67
80
|
.withToken(argv.token)
|
|
68
81
|
.withForce(argv.force)
|
|
69
82
|
.withAssumeYes(argv.yes)
|
|
83
|
+
.withOrg(argv.org)
|
|
84
|
+
.withSite(argv.site)
|
|
70
85
|
.withRootPath(rootPath)
|
|
71
86
|
.run();
|
|
72
87
|
},
|
|
@@ -10,6 +10,9 @@
|
|
|
10
10
|
* governing permissions and limitations under the License.
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
|
+
import path from 'path';
|
|
14
|
+
import fse from 'fs-extra';
|
|
15
|
+
|
|
13
16
|
export const CONTENT_DIR = 'content';
|
|
14
17
|
export const CONFIG_FILE = '.da-config.json';
|
|
15
18
|
export const GIT_AUTHOR = { name: 'aem-cli', email: 'aem-cli@adobe.com' };
|
|
@@ -22,6 +25,28 @@ export const LARGE_CLONE_FILE_THRESHOLD = 10000;
|
|
|
22
25
|
*/
|
|
23
26
|
export const CONTENT_IO_CONCURRENCY = 10;
|
|
24
27
|
|
|
28
|
+
/**
|
|
29
|
+
* Reads and normalizes the content config from a content directory.
|
|
30
|
+
* Accepts both current keys (org/site) and legacy keys (owner/repo) written
|
|
31
|
+
* by earlier versions of clone. org/site take priority when both are present.
|
|
32
|
+
* @param {string} contentDir - absolute path to the content/ directory
|
|
33
|
+
* @returns {Promise<{org: string, site: string, rootPath: string|undefined}>}
|
|
34
|
+
* @throws if the config file is missing or org/site cannot be resolved
|
|
35
|
+
*/
|
|
36
|
+
export async function readContentConfig(contentDir) {
|
|
37
|
+
const configPath = path.join(contentDir, CONFIG_FILE);
|
|
38
|
+
if (!await fse.pathExists(configPath)) {
|
|
39
|
+
throw new Error(`No config found at ${configPath}. Run 'aem content clone' first.`);
|
|
40
|
+
}
|
|
41
|
+
const raw = await fse.readJson(configPath);
|
|
42
|
+
const org = (raw.org || raw.owner || '').toLowerCase();
|
|
43
|
+
const site = (raw.site || raw.repo || '').toLowerCase();
|
|
44
|
+
if (!org || !site) {
|
|
45
|
+
throw new Error(`Invalid config: org and site are required in ${configPath}.`);
|
|
46
|
+
}
|
|
47
|
+
return { org, site, rootPath: raw.rootPath };
|
|
48
|
+
}
|
|
49
|
+
|
|
25
50
|
/**
|
|
26
51
|
* Normalizes a da.live path: leading slash, no trailing slash except root.
|
|
27
52
|
* @param {string} input
|
package/src/content/da-api.js
CHANGED
|
@@ -39,12 +39,12 @@ export class DaClient {
|
|
|
39
39
|
/**
|
|
40
40
|
* Lists the contents of a directory, following {@link LIST_CONTINUATION_HEADER} until complete.
|
|
41
41
|
* @param {string} org
|
|
42
|
-
* @param {string}
|
|
42
|
+
* @param {string} site
|
|
43
43
|
* @param {string} daPath - path starting with /
|
|
44
44
|
* @returns {Promise<Array<{path, name, ext?, lastModified}>>}
|
|
45
45
|
*/
|
|
46
|
-
async list(org,
|
|
47
|
-
const url = `${DA_ADMIN}/list/${org}/${
|
|
46
|
+
async list(org, site, daPath) {
|
|
47
|
+
const url = `${DA_ADMIN}/list/${org}/${site}${daPath}`;
|
|
48
48
|
const aggregated = [];
|
|
49
49
|
let continuation = null;
|
|
50
50
|
|
|
@@ -81,13 +81,13 @@ export class DaClient {
|
|
|
81
81
|
/**
|
|
82
82
|
* Recursively lists all files under a path using a non-recursive queue-based approach.
|
|
83
83
|
* @param {string} org
|
|
84
|
-
* @param {string}
|
|
84
|
+
* @param {string} site
|
|
85
85
|
* @param {string} [daPath='/']
|
|
86
86
|
* @param {(discoveredCount: number) => void} [onDiscovered] - cumulative file count per discovery
|
|
87
87
|
* @returns {Promise<Array<{path, name, ext, lastModified}>>}
|
|
88
88
|
*/
|
|
89
|
-
async listAll(org,
|
|
90
|
-
const prefix = `/${org}/${
|
|
89
|
+
async listAll(org, site, daPath = '/', onDiscovered = undefined) {
|
|
90
|
+
const prefix = `/${org}/${site}`;
|
|
91
91
|
const files = [];
|
|
92
92
|
let dirsToProcess = [daPath];
|
|
93
93
|
|
|
@@ -97,7 +97,7 @@ export class DaClient {
|
|
|
97
97
|
await processQueue(
|
|
98
98
|
dirsToProcess,
|
|
99
99
|
async (currentPath) => {
|
|
100
|
-
const items = await this.list(org,
|
|
100
|
+
const items = await this.list(org, site, currentPath);
|
|
101
101
|
for (const item of items) {
|
|
102
102
|
if (item.ext !== undefined) {
|
|
103
103
|
files.push(item);
|
|
@@ -119,10 +119,13 @@ export class DaClient {
|
|
|
119
119
|
|
|
120
120
|
/**
|
|
121
121
|
* Fetches the raw content of a file.
|
|
122
|
+
* @param {string} org
|
|
123
|
+
* @param {string} site
|
|
124
|
+
* @param {string} daPath
|
|
122
125
|
* @returns {Promise<Response|null>}
|
|
123
126
|
*/
|
|
124
|
-
async getSource(org,
|
|
125
|
-
const url = `${DA_ADMIN}/source/${org}/${
|
|
127
|
+
async getSource(org, site, daPath) {
|
|
128
|
+
const url = `${DA_ADMIN}/source/${org}/${site}${daPath}`;
|
|
126
129
|
const res = await this.fetch(url, { headers: this.authHeader });
|
|
127
130
|
if (res.status === 401) {
|
|
128
131
|
throw new Error('Unauthorized: invalid or missing token');
|
|
@@ -139,14 +142,14 @@ export class DaClient {
|
|
|
139
142
|
/**
|
|
140
143
|
* Uploads a file via PUT.
|
|
141
144
|
* @param {string} org
|
|
142
|
-
* @param {string}
|
|
145
|
+
* @param {string} site
|
|
143
146
|
* @param {string} daPath
|
|
144
147
|
* @param {Buffer} buffer
|
|
145
148
|
* @param {string} contentType
|
|
146
149
|
* @returns {Promise<object>} API response body
|
|
147
150
|
*/
|
|
148
|
-
async putSource(org,
|
|
149
|
-
const url = `${DA_ADMIN}/source/${org}/${
|
|
151
|
+
async putSource(org, site, daPath, buffer, contentType) {
|
|
152
|
+
const url = `${DA_ADMIN}/source/${org}/${site}${daPath}`;
|
|
150
153
|
const res = await this.fetch(url, {
|
|
151
154
|
method: 'PUT',
|
|
152
155
|
headers: { ...this.authHeader, 'Content-Type': contentType },
|
|
@@ -165,8 +168,8 @@ export class DaClient {
|
|
|
165
168
|
* Deletes a file or folder. Idempotent: 404 is treated as success.
|
|
166
169
|
* Throws on transport or server errors so callers don't silently treat them as success.
|
|
167
170
|
*/
|
|
168
|
-
async deleteSource(org,
|
|
169
|
-
const url = `${DA_ADMIN}/source/${org}/${
|
|
171
|
+
async deleteSource(org, site, daPath) {
|
|
172
|
+
const url = `${DA_ADMIN}/source/${org}/${site}${daPath}`;
|
|
170
173
|
const res = await this.fetch(url, {
|
|
171
174
|
method: 'DELETE',
|
|
172
175
|
headers: this.authHeader,
|
|
@@ -183,12 +186,12 @@ export class DaClient {
|
|
|
183
186
|
/**
|
|
184
187
|
* Returns the current lastModified for a file via a HEAD request.
|
|
185
188
|
* @param {string} org
|
|
186
|
-
* @param {string}
|
|
189
|
+
* @param {string} site
|
|
187
190
|
* @param {string} daPath - e.g. /blog/post.html
|
|
188
191
|
* @returns {Promise<number|null>}
|
|
189
192
|
*/
|
|
190
|
-
async getRemoteLastModified(org,
|
|
191
|
-
const url = `${DA_ADMIN}/source/${org}/${
|
|
193
|
+
async getRemoteLastModified(org, site, daPath) {
|
|
194
|
+
const url = `${DA_ADMIN}/source/${org}/${site}${daPath}`;
|
|
192
195
|
const res = await this.fetch(url, { method: 'HEAD', headers: this.authHeader });
|
|
193
196
|
if (res.status === 401) {
|
|
194
197
|
throw new Error('Unauthorized: invalid or missing token');
|
package/src/content/diff.cmd.js
CHANGED
|
@@ -17,7 +17,7 @@ import { createTwoFilesPatch } from 'diff';
|
|
|
17
17
|
import chalk from 'chalk-template';
|
|
18
18
|
import { DaClient } from './da-api.js';
|
|
19
19
|
import { getValidToken } from './da-auth.js';
|
|
20
|
-
import { CONTENT_DIR,
|
|
20
|
+
import { CONTENT_DIR, readContentConfig } from './content-shared.js';
|
|
21
21
|
|
|
22
22
|
function printPatch(patch) {
|
|
23
23
|
for (const line of patch.split('\n')) {
|
|
@@ -60,12 +60,7 @@ export default class DiffCommand {
|
|
|
60
60
|
async run() {
|
|
61
61
|
const { log } = this;
|
|
62
62
|
const contentDir = path.resolve(this._dir, CONTENT_DIR);
|
|
63
|
-
const
|
|
64
|
-
|
|
65
|
-
if (!await fse.pathExists(configPath)) {
|
|
66
|
-
throw new Error('No config found. Run \'aem content clone\' first.');
|
|
67
|
-
}
|
|
68
|
-
const { org, repo } = await fse.readJson(configPath);
|
|
63
|
+
const { org, site } = await readContentConfig(contentDir);
|
|
69
64
|
|
|
70
65
|
const matrix = await git.statusMatrix({ fs, dir: contentDir });
|
|
71
66
|
let changedPaths = matrix
|
|
@@ -98,7 +93,7 @@ export default class DiffCommand {
|
|
|
98
93
|
? await fse.readFile(localPath)
|
|
99
94
|
: Buffer.from('');
|
|
100
95
|
|
|
101
|
-
const remoteRes = await client.getSource(org,
|
|
96
|
+
const remoteRes = await client.getSource(org, site, daPath);
|
|
102
97
|
|
|
103
98
|
const localText = localBuffer.toString('utf-8');
|
|
104
99
|
const remoteText = remoteRes ? await remoteRes.text() : '';
|
package/src/content/merge.cmd.js
CHANGED
|
@@ -16,7 +16,7 @@ import git from 'isomorphic-git';
|
|
|
16
16
|
import { diff3Merge } from 'node-diff3'; // eslint-disable-line import/no-unresolved
|
|
17
17
|
import { DaClient } from './da-api.js';
|
|
18
18
|
import { getValidToken } from './da-auth.js';
|
|
19
|
-
import { CONTENT_DIR,
|
|
19
|
+
import { CONTENT_DIR, readContentConfig } from './content-shared.js';
|
|
20
20
|
|
|
21
21
|
/**
|
|
22
22
|
* 3-way merge using diff3 algorithm.
|
|
@@ -69,12 +69,7 @@ export default class MergeCommand {
|
|
|
69
69
|
async run() {
|
|
70
70
|
const { log } = this;
|
|
71
71
|
const contentDir = path.resolve(this._dir, CONTENT_DIR);
|
|
72
|
-
const
|
|
73
|
-
|
|
74
|
-
if (!await fse.pathExists(configPath)) {
|
|
75
|
-
throw new Error('No config found. Run \'aem content clone\' first.');
|
|
76
|
-
}
|
|
77
|
-
const { org, repo } = await fse.readJson(configPath);
|
|
72
|
+
const { org, site } = await readContentConfig(contentDir);
|
|
78
73
|
|
|
79
74
|
// Find locally changed files via git
|
|
80
75
|
const matrix = await git.statusMatrix({ fs, dir: contentDir });
|
|
@@ -112,7 +107,7 @@ export default class MergeCommand {
|
|
|
112
107
|
// eslint-disable-next-line no-await-in-loop
|
|
113
108
|
const [localBuffer, remoteRes, blobResult] = await Promise.all([
|
|
114
109
|
fse.readFile(localPath),
|
|
115
|
-
client.getSource(org,
|
|
110
|
+
client.getSource(org, site, daPath),
|
|
116
111
|
git.readBlob({
|
|
117
112
|
fs, dir: contentDir, oid: headCommit.oid, filepath: relPath,
|
|
118
113
|
})
|
package/src/content/push.cmd.js
CHANGED
|
@@ -18,8 +18,8 @@ import { DaClient, getContentType } from './da-api.js';
|
|
|
18
18
|
import { getValidToken } from './da-auth.js';
|
|
19
19
|
import {
|
|
20
20
|
CONTENT_DIR,
|
|
21
|
-
CONFIG_FILE,
|
|
22
21
|
CONTENT_IO_CONCURRENCY,
|
|
22
|
+
readContentConfig,
|
|
23
23
|
} from './content-shared.js';
|
|
24
24
|
import {
|
|
25
25
|
resolveSyncedOid,
|
|
@@ -67,19 +67,19 @@ export default class PushCommand {
|
|
|
67
67
|
* Checks for conflicts between local changes and remote modifications.
|
|
68
68
|
* @param {DaClient} client
|
|
69
69
|
* @param {string} org
|
|
70
|
-
* @param {string}
|
|
70
|
+
* @param {string} site
|
|
71
71
|
* @param {string[]} modified
|
|
72
72
|
* @param {string[]} deleted
|
|
73
73
|
* @param {number} lastSyncTime
|
|
74
74
|
* @returns {Promise<boolean>} true if the push should be aborted
|
|
75
75
|
*/
|
|
76
|
-
async _checkConflicts(client, org,
|
|
76
|
+
async _checkConflicts(client, org, site, modified, deleted, lastSyncTime) {
|
|
77
77
|
const { log } = this;
|
|
78
78
|
const conflicts = [];
|
|
79
79
|
|
|
80
80
|
for (const daPath of [...modified, ...deleted]) {
|
|
81
81
|
// eslint-disable-next-line no-await-in-loop
|
|
82
|
-
const remoteLastModified = await client.getRemoteLastModified(org,
|
|
82
|
+
const remoteLastModified = await client.getRemoteLastModified(org, site, daPath);
|
|
83
83
|
if (remoteLastModified != null && remoteLastModified > lastSyncTime) {
|
|
84
84
|
conflicts.push({ daPath, remoteDate: new Date(remoteLastModified).toLocaleString() });
|
|
85
85
|
}
|
|
@@ -105,12 +105,12 @@ export default class PushCommand {
|
|
|
105
105
|
* Uploads added and modified files to da.live.
|
|
106
106
|
* @param {DaClient} client
|
|
107
107
|
* @param {string} org
|
|
108
|
-
* @param {string}
|
|
108
|
+
* @param {string} site
|
|
109
109
|
* @param {string} contentDir
|
|
110
110
|
* @param {string[]} targets
|
|
111
111
|
* @returns {Promise<{ pushed: number, errors: number }>}
|
|
112
112
|
*/
|
|
113
|
-
async _uploadFiles(client, org,
|
|
113
|
+
async _uploadFiles(client, org, site, contentDir, targets) {
|
|
114
114
|
const { log } = this;
|
|
115
115
|
let pushed = 0;
|
|
116
116
|
let errors = 0;
|
|
@@ -123,7 +123,7 @@ export default class PushCommand {
|
|
|
123
123
|
const ext = daPath.split('.').pop();
|
|
124
124
|
try {
|
|
125
125
|
const buffer = await fse.readFile(localPath);
|
|
126
|
-
await client.putSource(org,
|
|
126
|
+
await client.putSource(org, site, daPath, buffer, getContentType(ext));
|
|
127
127
|
log.info(` ✓ ${daPath}`);
|
|
128
128
|
return { ok: true };
|
|
129
129
|
} catch (err) {
|
|
@@ -148,11 +148,11 @@ export default class PushCommand {
|
|
|
148
148
|
* Deletes files from da.live.
|
|
149
149
|
* @param {DaClient} client
|
|
150
150
|
* @param {string} org
|
|
151
|
-
* @param {string}
|
|
151
|
+
* @param {string} site
|
|
152
152
|
* @param {string[]} deleted
|
|
153
153
|
* @returns {Promise<{ pushed: number, errors: number }>}
|
|
154
154
|
*/
|
|
155
|
-
async _deleteFiles(client, org,
|
|
155
|
+
async _deleteFiles(client, org, site, deleted) {
|
|
156
156
|
const { log } = this;
|
|
157
157
|
let pushed = 0;
|
|
158
158
|
let errors = 0;
|
|
@@ -162,7 +162,7 @@ export default class PushCommand {
|
|
|
162
162
|
[...deleted],
|
|
163
163
|
async (daPath) => {
|
|
164
164
|
try {
|
|
165
|
-
await client.deleteSource(org,
|
|
165
|
+
await client.deleteSource(org, site, daPath);
|
|
166
166
|
log.info(` ✓ deleted ${daPath}`);
|
|
167
167
|
return { ok: true };
|
|
168
168
|
} catch (err) {
|
|
@@ -186,12 +186,7 @@ export default class PushCommand {
|
|
|
186
186
|
async run() {
|
|
187
187
|
const { log } = this;
|
|
188
188
|
const contentDir = path.resolve(this._dir, CONTENT_DIR);
|
|
189
|
-
const
|
|
190
|
-
|
|
191
|
-
if (!await fse.pathExists(configPath)) {
|
|
192
|
-
throw new Error(`No config found at ${configPath}. Run 'aem content clone' first.`);
|
|
193
|
-
}
|
|
194
|
-
const { org, repo } = await fse.readJson(configPath);
|
|
189
|
+
const { org, site } = await readContentConfig(contentDir);
|
|
195
190
|
|
|
196
191
|
const matrix = await git.statusMatrix({ fs, dir: contentDir });
|
|
197
192
|
if (statusMatrixHasUncommitted(matrix)) {
|
|
@@ -228,7 +223,7 @@ export default class PushCommand {
|
|
|
228
223
|
|
|
229
224
|
const token = await getValidToken(log, this._token, this._dir);
|
|
230
225
|
|
|
231
|
-
log.info(`Pushing content to da.live: ${org}/${
|
|
226
|
+
log.info(`Pushing content to da.live: ${org}/${site}`);
|
|
232
227
|
log.info(`${added.length} added, ${modified.length} modified, ${deleted.length} deleted`);
|
|
233
228
|
|
|
234
229
|
const client = new DaClient(token);
|
|
@@ -236,7 +231,7 @@ export default class PushCommand {
|
|
|
236
231
|
const shouldAbort = await this._checkConflicts(
|
|
237
232
|
client,
|
|
238
233
|
org,
|
|
239
|
-
|
|
234
|
+
site,
|
|
240
235
|
modified,
|
|
241
236
|
deleted,
|
|
242
237
|
lastSyncTime,
|
|
@@ -271,12 +266,12 @@ export default class PushCommand {
|
|
|
271
266
|
const {
|
|
272
267
|
pushed: putPushed,
|
|
273
268
|
errors: putErrors,
|
|
274
|
-
} = await this._uploadFiles(client, org,
|
|
269
|
+
} = await this._uploadFiles(client, org, site, contentDir, [...added, ...modified]);
|
|
275
270
|
|
|
276
271
|
const {
|
|
277
272
|
pushed: deletePushed,
|
|
278
273
|
errors: deleteErrors,
|
|
279
|
-
} = await this._deleteFiles(client, org,
|
|
274
|
+
} = await this._deleteFiles(client, org, site, deleted);
|
|
280
275
|
|
|
281
276
|
const pushed = putPushed + deletePushed;
|
|
282
277
|
const pushErrors = putErrors + deleteErrors;
|
|
@@ -11,9 +11,8 @@
|
|
|
11
11
|
*/
|
|
12
12
|
import fs from 'fs';
|
|
13
13
|
import path from 'path';
|
|
14
|
-
import fse from 'fs-extra';
|
|
15
14
|
import git from 'isomorphic-git';
|
|
16
|
-
import { CONTENT_DIR,
|
|
15
|
+
import { CONTENT_DIR, readContentConfig } from './content-shared.js';
|
|
17
16
|
import { resolveSyncedOid, countCommitsAhead } from './content-git.js';
|
|
18
17
|
|
|
19
18
|
export default class StatusCommand {
|
|
@@ -30,14 +29,9 @@ export default class StatusCommand {
|
|
|
30
29
|
async run() {
|
|
31
30
|
const { log } = this;
|
|
32
31
|
const contentDir = path.resolve(this._dir, CONTENT_DIR);
|
|
33
|
-
const
|
|
32
|
+
const { org, site } = await readContentConfig(contentDir);
|
|
34
33
|
|
|
35
|
-
|
|
36
|
-
throw new Error('No config found. Run \'aem content clone\' first.');
|
|
37
|
-
}
|
|
38
|
-
const { org, repo } = await fse.readJson(configPath);
|
|
39
|
-
|
|
40
|
-
log.info(`On da.live: ${org}/${repo}`);
|
|
34
|
+
log.info(`On da.live: ${org}/${site}`);
|
|
41
35
|
log.info(`Local content: ./${CONTENT_DIR}/\n`);
|
|
42
36
|
|
|
43
37
|
const matrix = await git.statusMatrix({ fs, dir: contentDir });
|