cloud-ide-cide 2.0.34
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/buildAllProjects.js +131 -0
- package/buildProject.js +209 -0
- package/buildWorkspace.js +225 -0
- package/cideShell.js +1292 -0
- package/cli.js +521 -0
- package/createProject.js +71 -0
- package/deployer/node/upload-api.js +265 -0
- package/deployer/php/setup.php +332 -0
- package/deployer/php/upload-ui.php +294 -0
- package/package.json +53 -0
- package/publishPackage.js +969 -0
- package/resolveNgProjectName.js +94 -0
- package/serverInit.js +665 -0
- package/startProject.js +57 -0
- package/uploadProject.js +727 -0
- package/watchLinkProject.js +40 -0
|
@@ -0,0 +1,969 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* cide publish — interactive multi-package publish to GitHub Packages,
|
|
3
|
+
* with workspace-wide dependent updates (file: and ^ → ^newVersion).
|
|
4
|
+
*
|
|
5
|
+
* Dependent refresh skips `<angularRoot>/<dist>` (ng-packagr output): those package.json
|
|
6
|
+
* files are not workspace consumers, and `npm install` there caused duplicate @angular
|
|
7
|
+
* installs and flaky DI / InputSignal types.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
const { execSync } = require('child_process');
|
|
11
|
+
const fs = require('fs');
|
|
12
|
+
const path = require('path');
|
|
13
|
+
const readline = require('readline');
|
|
14
|
+
const { resolveNgProjectNameForPackageDir } = require('./resolveNgProjectName');
|
|
15
|
+
|
|
16
|
+
const GPR_HOST = 'npm.pkg.github.com';
|
|
17
|
+
|
|
18
|
+
function existsSync(p) {
|
|
19
|
+
try {
|
|
20
|
+
fs.accessSync(p);
|
|
21
|
+
return true;
|
|
22
|
+
} catch {
|
|
23
|
+
return false;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/** True if `childPath` is `ancestorPath` or a file/folder inside it (Windows-safe). */
|
|
28
|
+
function isPathInsideOrEqual(childPath, ancestorPath) {
|
|
29
|
+
const c = path.resolve(childPath);
|
|
30
|
+
const a = path.resolve(ancestorPath);
|
|
31
|
+
if (c === a) return true;
|
|
32
|
+
const rel = path.relative(a, c);
|
|
33
|
+
return rel !== '' && !rel.startsWith('..') && !path.isAbsolute(rel);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function readJson(p) {
|
|
37
|
+
return JSON.parse(fs.readFileSync(p, 'utf8'));
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function writeJson(p, obj) {
|
|
41
|
+
fs.writeFileSync(p, JSON.stringify(obj, null, 2) + '\n', 'utf8');
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function isGithubPackagesPublishable(pkg) {
|
|
45
|
+
const r = pkg && pkg.publishConfig && pkg.publishConfig.registry;
|
|
46
|
+
return typeof r === 'string' && r.includes(GPR_HOST);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function bumpSemver(version, kind) {
|
|
50
|
+
const m = String(version).match(/^(\d+)\.(\d+)\.(\d+)(.*)?$/);
|
|
51
|
+
if (!m) throw new Error(`Invalid semver: ${version}`);
|
|
52
|
+
let major = parseInt(m[1], 10);
|
|
53
|
+
let minor = parseInt(m[2], 10);
|
|
54
|
+
let patch = parseInt(m[3], 10);
|
|
55
|
+
const rest = m[4] || '';
|
|
56
|
+
if (kind === 'major') {
|
|
57
|
+
major += 1;
|
|
58
|
+
minor = 0;
|
|
59
|
+
patch = 0;
|
|
60
|
+
} else if (kind === 'minor') {
|
|
61
|
+
minor += 1;
|
|
62
|
+
patch = 0;
|
|
63
|
+
} else {
|
|
64
|
+
patch += 1;
|
|
65
|
+
}
|
|
66
|
+
return `${major}.${minor}.${patch}${rest}`;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function question(rl, prompt) {
|
|
70
|
+
return new Promise((resolve) => {
|
|
71
|
+
rl.question(prompt, (answer) => resolve(answer));
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Resolve workspace: angularRoot = cwd (where cide.json lives),
|
|
77
|
+
* monorepoRoot = workspace_root relative to angularRoot (default ".")
|
|
78
|
+
*/
|
|
79
|
+
function loadCideConfig(angularRoot) {
|
|
80
|
+
const cidePath = path.join(angularRoot, 'cide.json');
|
|
81
|
+
if (!existsSync(cidePath)) {
|
|
82
|
+
return { cide: {}, angularRoot, monorepoRoot: angularRoot, cidePath: null };
|
|
83
|
+
}
|
|
84
|
+
const cide = readJson(cidePath);
|
|
85
|
+
const wr = cide.workspace_root != null ? cide.workspace_root : '.';
|
|
86
|
+
const monorepoRoot = path.resolve(angularRoot, wr);
|
|
87
|
+
return { cide, angularRoot, monorepoRoot, cidePath };
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function safeReaddir(dir) {
|
|
91
|
+
try {
|
|
92
|
+
return fs.readdirSync(dir, { withFileTypes: true });
|
|
93
|
+
} catch {
|
|
94
|
+
return [];
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Angular lib dist output: ng-packagr "dest" is relative to the project folder
|
|
100
|
+
* (e.g. ../../dist/@cloudidesys/cloud-ide-element). Do not assume dist/<folderName>.
|
|
101
|
+
*/
|
|
102
|
+
function resolveAngularDistDir(angularRoot, distPath, entry) {
|
|
103
|
+
const ngPkgPath = path.join(entry.absDir, 'ng-package.json');
|
|
104
|
+
if (existsSync(ngPkgPath)) {
|
|
105
|
+
try {
|
|
106
|
+
const ngPkg = readJson(ngPkgPath);
|
|
107
|
+
if (ngPkg.dest) {
|
|
108
|
+
return path.resolve(entry.absDir, ngPkg.dest);
|
|
109
|
+
}
|
|
110
|
+
} catch {
|
|
111
|
+
/* fall through */
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
const scopedDist = path.join(angularRoot, distPath, '@cloudidesys', entry.folderName);
|
|
115
|
+
if (existsSync(scopedDist)) return scopedDist;
|
|
116
|
+
return path.join(angularRoot, distPath, entry.folderName);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Collect publishable packages: projects/* under angular root, sibling_packages, and monorepo children with publishConfig.
|
|
121
|
+
*/
|
|
122
|
+
function discoverPublishablePackages({ cide, angularRoot, monorepoRoot }) {
|
|
123
|
+
const seen = new Set();
|
|
124
|
+
const list = [];
|
|
125
|
+
|
|
126
|
+
// workspaceRoot defaults to angularRoot; sibling Angular workspaces override this per-entry
|
|
127
|
+
function add(absDir, kind, labelRel, workspaceRoot) {
|
|
128
|
+
const resolved = path.resolve(absDir);
|
|
129
|
+
if (seen.has(resolved)) return;
|
|
130
|
+
const pj = path.join(absDir, 'package.json');
|
|
131
|
+
if (!existsSync(pj)) return;
|
|
132
|
+
let pkg;
|
|
133
|
+
try {
|
|
134
|
+
pkg = readJson(pj);
|
|
135
|
+
} catch {
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
if (!isGithubPackagesPublishable(pkg)) return;
|
|
139
|
+
seen.add(resolved);
|
|
140
|
+
const folderName = path.basename(absDir);
|
|
141
|
+
list.push({
|
|
142
|
+
absDir: resolved,
|
|
143
|
+
packageJsonPath: pj,
|
|
144
|
+
name: pkg.name,
|
|
145
|
+
version: pkg.version || '0.0.0',
|
|
146
|
+
folderName,
|
|
147
|
+
kind, // 'angular-lib' | 'node-package'
|
|
148
|
+
labelRel,
|
|
149
|
+
workspaceRoot: path.resolve(workspaceRoot || angularRoot),
|
|
150
|
+
hasBuildScript: !!(pkg.scripts && pkg.scripts.build),
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// Angular libraries: angularRoot/projects/* and projects/@cloudidesys/* (scoped layout)
|
|
155
|
+
const projectsDir = path.join(angularRoot, 'projects');
|
|
156
|
+
if (existsSync(projectsDir)) {
|
|
157
|
+
for (const e of safeReaddir(projectsDir)) {
|
|
158
|
+
if (!e.isDirectory()) continue;
|
|
159
|
+
if (e.name === '@cloudidesys') {
|
|
160
|
+
const scoped = path.join(projectsDir, e.name);
|
|
161
|
+
for (const e2 of safeReaddir(scoped)) {
|
|
162
|
+
if (!e2.isDirectory()) continue;
|
|
163
|
+
add(path.join(scoped, e2.name), 'angular-lib', path.join('projects', '@cloudidesys', e2.name));
|
|
164
|
+
}
|
|
165
|
+
continue;
|
|
166
|
+
}
|
|
167
|
+
add(path.join(projectsDir, e.name), 'angular-lib', path.join('projects', e.name));
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// Explicit sibling_packages (paths relative to angularRoot)
|
|
172
|
+
// If a sibling is an Angular workspace (has angular.json + projects/), scan its projects too.
|
|
173
|
+
const siblings = Array.isArray(cide.sibling_packages) ? cide.sibling_packages : [];
|
|
174
|
+
for (const rel of siblings) {
|
|
175
|
+
const abs = path.resolve(angularRoot, rel);
|
|
176
|
+
const siblingProjectsDir = path.join(abs, 'projects');
|
|
177
|
+
const siblingAngularJson = path.join(abs, 'angular.json');
|
|
178
|
+
if (existsSync(siblingAngularJson) && existsSync(siblingProjectsDir)) {
|
|
179
|
+
// Angular workspace sibling — scan its projects/* for publishable libs, build from sibling root
|
|
180
|
+
for (const e of safeReaddir(siblingProjectsDir)) {
|
|
181
|
+
if (!e.isDirectory()) continue;
|
|
182
|
+
if (e.name === '@cloudidesys') {
|
|
183
|
+
const scoped = path.join(siblingProjectsDir, e.name);
|
|
184
|
+
for (const e2 of safeReaddir(scoped)) {
|
|
185
|
+
if (!e2.isDirectory()) continue;
|
|
186
|
+
add(path.join(scoped, e2.name), 'angular-lib', path.join(rel, 'projects', '@cloudidesys', e2.name), abs);
|
|
187
|
+
}
|
|
188
|
+
continue;
|
|
189
|
+
}
|
|
190
|
+
add(path.join(siblingProjectsDir, e.name), 'angular-lib', path.join(rel, 'projects', e.name), abs);
|
|
191
|
+
}
|
|
192
|
+
} else {
|
|
193
|
+
add(abs, 'node-package', rel.replace(/\\/g, '/'));
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// Auto: immediate children of monorepoRoot with publishable package.json
|
|
198
|
+
for (const e of safeReaddir(monorepoRoot)) {
|
|
199
|
+
if (!e.isDirectory()) continue;
|
|
200
|
+
if (e.name === 'node_modules' || e.name.startsWith('.')) continue;
|
|
201
|
+
const abs = path.join(monorepoRoot, e.name);
|
|
202
|
+
if (path.resolve(abs) === path.resolve(angularRoot)) continue;
|
|
203
|
+
add(abs, 'node-package', e.name);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// Angular workspace root app package.json if publishable (unusual)
|
|
207
|
+
add(angularRoot, 'node-package', '.');
|
|
208
|
+
|
|
209
|
+
return list;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
function parseSelection(input, maxN) {
|
|
213
|
+
const s = String(input).trim().toLowerCase();
|
|
214
|
+
if (s === 'q' || s === 'quit' || s === '') return null;
|
|
215
|
+
if (s === 'all') {
|
|
216
|
+
return Array.from({ length: maxN }, (_, i) => i);
|
|
217
|
+
}
|
|
218
|
+
const set = new Set();
|
|
219
|
+
const parts = s.split(',').map((p) => p.trim()).filter(Boolean);
|
|
220
|
+
for (const part of parts) {
|
|
221
|
+
if (part.includes('-')) {
|
|
222
|
+
const [a, b] = part.split('-').map((x) => parseInt(x.trim(), 10));
|
|
223
|
+
if (Number.isFinite(a) && Number.isFinite(b)) {
|
|
224
|
+
const lo = Math.min(a, b);
|
|
225
|
+
const hi = Math.max(a, b);
|
|
226
|
+
for (let i = lo; i <= hi; i++) {
|
|
227
|
+
if (i >= 1 && i <= maxN) set.add(i - 1);
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
} else {
|
|
231
|
+
const n = parseInt(part, 10);
|
|
232
|
+
if (n >= 1 && n <= maxN) set.add(n - 1);
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
return [...set].sort((a, b) => a - b);
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/** Loose semver X.Y.Z (allows pre-release suffix) */
|
|
239
|
+
function isValidSemverString(v) {
|
|
240
|
+
return /^\d+\.\d+\.\d+/.test(String(v).trim());
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/**
|
|
244
|
+
* Parse --packages / interactive input.
|
|
245
|
+
* Form A: 1,3,5 | 1-5 | all → indices only, versionByIndex null
|
|
246
|
+
* Form B: 1=2.0.1,4=1.2.0 → each index gets its version (1-based index in list)
|
|
247
|
+
*/
|
|
248
|
+
function parsePackagePublishSpec(raw, maxN) {
|
|
249
|
+
const s = String(raw).trim();
|
|
250
|
+
if (!s || s.toLowerCase() === 'q' || s.toLowerCase() === 'quit') {
|
|
251
|
+
return { ok: false, error: 'cancelled', indices: [], versionByIndex: null };
|
|
252
|
+
}
|
|
253
|
+
if (s.toLowerCase() === 'all') {
|
|
254
|
+
return {
|
|
255
|
+
ok: true,
|
|
256
|
+
indices: Array.from({ length: maxN }, (_, i) => i),
|
|
257
|
+
versionByIndex: null,
|
|
258
|
+
};
|
|
259
|
+
}
|
|
260
|
+
if (s.includes('=')) {
|
|
261
|
+
const versionByIndex = new Map();
|
|
262
|
+
const parts = s.split(',').map((p) => p.trim()).filter(Boolean);
|
|
263
|
+
for (const part of parts) {
|
|
264
|
+
const eq = part.indexOf('=');
|
|
265
|
+
if (eq < 0) {
|
|
266
|
+
return {
|
|
267
|
+
ok: false,
|
|
268
|
+
error: `Mixed format: use only "num=version" pairs in one line (got: "${part}").`,
|
|
269
|
+
indices: [],
|
|
270
|
+
versionByIndex: null,
|
|
271
|
+
};
|
|
272
|
+
}
|
|
273
|
+
const idxStr = part.slice(0, eq).trim();
|
|
274
|
+
const ver = part.slice(eq + 1).trim();
|
|
275
|
+
const idx1 = parseInt(idxStr, 10);
|
|
276
|
+
if (!Number.isFinite(idx1) || idx1 < 1 || idx1 > maxN) {
|
|
277
|
+
return { ok: false, error: `Invalid index: ${idxStr} (valid 1–${maxN})`, indices: [], versionByIndex: null };
|
|
278
|
+
}
|
|
279
|
+
if (!isValidSemverString(ver)) {
|
|
280
|
+
return { ok: false, error: `Invalid version for #${idx1}: "${ver}" (need e.g. 2.0.1)`, indices: [], versionByIndex: null };
|
|
281
|
+
}
|
|
282
|
+
versionByIndex.set(idx1 - 1, ver);
|
|
283
|
+
}
|
|
284
|
+
const indices = [...versionByIndex.keys()].sort((a, b) => a - b);
|
|
285
|
+
return { ok: true, indices, versionByIndex };
|
|
286
|
+
}
|
|
287
|
+
const indices = parseSelection(s, maxN);
|
|
288
|
+
if (indices === null || indices.length === 0) {
|
|
289
|
+
return { ok: false, error: 'No valid package numbers.', indices: [], versionByIndex: null };
|
|
290
|
+
}
|
|
291
|
+
return { ok: true, indices, versionByIndex: null };
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
function printPublishUsageBox() {
|
|
295
|
+
console.log(`
|
|
296
|
+
----------------------------------------------------------------
|
|
297
|
+
HOW TO PUBLISH — read this FIRST, then use the numbered list below
|
|
298
|
+
----------------------------------------------------------------
|
|
299
|
+
NUMBERS ONLY (then you will choose version or patch/minor/major):
|
|
300
|
+
1 single package from the list
|
|
301
|
+
1,3,5 several packages
|
|
302
|
+
1-5 range (1 through 5)
|
|
303
|
+
all every package in the list
|
|
304
|
+
|
|
305
|
+
NUMBERS + EXACT VERSION (no second question — each gets its version):
|
|
306
|
+
1=2.0.1 package [1] will be published as 2.0.1
|
|
307
|
+
1=2.0.1,4=1.2.0 package [1] as 2.0.1, package [4] as 1.2.0
|
|
308
|
+
|
|
309
|
+
CANCEL entire publish: q
|
|
310
|
+
----------------------------------------------------------------
|
|
311
|
+
Non-interactive (same rules; no prompts):
|
|
312
|
+
cide publish -p 1 --set-version 2.0.1
|
|
313
|
+
cide publish -p "1,4,5" --bump minor
|
|
314
|
+
cide publish -p "1=2.0.1,4=1.2.0" --global 3
|
|
315
|
+
cide publish --help ← full flags and more examples
|
|
316
|
+
|
|
317
|
+
After a failure (same -p list):
|
|
318
|
+
--resume-from N start at step N of the batch (skip 1..N-1); fix failed package.json if needed first
|
|
319
|
+
--continue-on-error keep going without stopping at the first failure
|
|
320
|
+
----------------------------------------------------------------
|
|
321
|
+
`);
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
/**
|
|
325
|
+
* After publish: optional npm install -g using SAME numbered list.
|
|
326
|
+
* Skip if user types: n | no | none | skip | 0 | - | empty
|
|
327
|
+
*/
|
|
328
|
+
function parseGlobalInstallSpec(raw, maxN) {
|
|
329
|
+
const s = String(raw).trim().toLowerCase();
|
|
330
|
+
if (!s || s === 'n' || s === 'no' || s === 'none' || s === 'skip' || s === '-' || s === '0' || s === 'q' || s === 'quit') {
|
|
331
|
+
return { ok: true, skip: true, indices: [] };
|
|
332
|
+
}
|
|
333
|
+
const indices = parseSelection(s, maxN);
|
|
334
|
+
if (indices === null || indices.length === 0) {
|
|
335
|
+
return { ok: false, skip: false, indices: [], error: 'No valid numbers. Type n to skip global install.' };
|
|
336
|
+
}
|
|
337
|
+
return { ok: true, skip: false, indices };
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
function printGlobalInstallUsageBox() {
|
|
341
|
+
console.log(`
|
|
342
|
+
----------------------------------------------------------------
|
|
343
|
+
GLOBAL npm install (optional) — uses the SAME numbers as the list
|
|
344
|
+
----------------------------------------------------------------
|
|
345
|
+
After publishing, you can install packages from the registry globally.
|
|
346
|
+
Type ONE line BEFORE you are asked — examples:
|
|
347
|
+
|
|
348
|
+
3 global-install only item [3] → npm install -g name@version
|
|
349
|
+
2,5 global-install [2] and [5]
|
|
350
|
+
1-4 global-install every item from [1] through [4]
|
|
351
|
+
all global-install every item in the list (use carefully)
|
|
352
|
+
|
|
353
|
+
SKIP (no global install) — type any of:
|
|
354
|
+
n no none skip 0 - q (or just press Enter)
|
|
355
|
+
|
|
356
|
+
Non-interactive (with publish):
|
|
357
|
+
cide publish -p 1 --set-version 2.0.1 --global 3
|
|
358
|
+
cide publish -p all --bump patch --global "2,5"
|
|
359
|
+
|
|
360
|
+
Standalone (no publish):
|
|
361
|
+
cide install-global
|
|
362
|
+
cide install-global -p 3
|
|
363
|
+
cide install-global --help
|
|
364
|
+
----------------------------------------------------------------
|
|
365
|
+
`);
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
function npmInstallGlobalForEntries(packages, indices, npmOpts = {}) {
|
|
369
|
+
const force = npmOpts.force ? ' --force' : '';
|
|
370
|
+
for (const idx of indices) {
|
|
371
|
+
const entry = packages[idx];
|
|
372
|
+
if (!entry) {
|
|
373
|
+
console.warn(` ⚠ Skip invalid index ${idx + 1}`);
|
|
374
|
+
continue;
|
|
375
|
+
}
|
|
376
|
+
const pkg = readJson(entry.packageJsonPath);
|
|
377
|
+
const name = pkg.name;
|
|
378
|
+
const version = pkg.version || '0.0.0';
|
|
379
|
+
if (!name) {
|
|
380
|
+
console.warn(` ⚠ Skip index ${idx + 1}: missing package name`);
|
|
381
|
+
continue;
|
|
382
|
+
}
|
|
383
|
+
const spec = `${name}@${version}`;
|
|
384
|
+
console.log(` npm install -g ${spec}${force ? ' --force' : ''}`);
|
|
385
|
+
try {
|
|
386
|
+
execSync(`npm install -g ${JSON.stringify(spec)}${force}`, { stdio: 'inherit' });
|
|
387
|
+
} catch (e) {
|
|
388
|
+
console.error(` ⚠ Global install failed for ${spec}:`, e.message);
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
async function runOptionalGlobalInstall(rl, packages, options) {
|
|
394
|
+
const maxN = packages.length;
|
|
395
|
+
const cliGlobal = options.global != null ? String(options.global).trim() : '';
|
|
396
|
+
|
|
397
|
+
if (cliGlobal.length > 0) {
|
|
398
|
+
const g = parseGlobalInstallSpec(cliGlobal, maxN);
|
|
399
|
+
if (!g.ok) {
|
|
400
|
+
console.error(g.error || 'Invalid --global');
|
|
401
|
+
return;
|
|
402
|
+
}
|
|
403
|
+
if (g.skip || g.indices.length === 0) {
|
|
404
|
+
console.log('\n(Skipping global install: --global is n / skip / empty.)\n');
|
|
405
|
+
return;
|
|
406
|
+
}
|
|
407
|
+
console.log('\n--- Global install (from --global) ---');
|
|
408
|
+
npmInstallGlobalForEntries(packages, g.indices, { force: !!options.force });
|
|
409
|
+
return;
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
if (options.packages != null && String(options.packages).trim() !== '') {
|
|
413
|
+
// Non-interactive publish without --global → skip global step
|
|
414
|
+
return;
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
printGlobalInstallUsageBox();
|
|
418
|
+
const raw = await question(rl, 'Global install: type numbers (or n / Enter to skip): ');
|
|
419
|
+
const g = parseGlobalInstallSpec(raw, maxN);
|
|
420
|
+
if (!g.ok) {
|
|
421
|
+
console.error(g.error || 'Invalid input.');
|
|
422
|
+
return;
|
|
423
|
+
}
|
|
424
|
+
if (g.skip || g.indices.length === 0) {
|
|
425
|
+
console.log('\n(No global install.)\n');
|
|
426
|
+
return;
|
|
427
|
+
}
|
|
428
|
+
console.log('\n--- Global install ---');
|
|
429
|
+
npmInstallGlobalForEntries(packages, g.indices, { force: !!options.force });
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
/**
|
|
433
|
+
* Standalone: cide install-global — npm install -g by workspace list index.
|
|
434
|
+
* @param {object} [options]
|
|
435
|
+
* @param {string} [options.packages] - same index spec as -p (1,3,5 | 1-5 | all | n)
|
|
436
|
+
* @param {import('readline').Interface} [options.readlineInterface] - Reuse shell readline when inside `cide shell`
|
|
437
|
+
* @param {boolean} [options.force] - Pass --force to npm install -g
|
|
438
|
+
*/
|
|
439
|
+
async function installGlobalPackage(options = {}) {
|
|
440
|
+
const cliPackages = options.packages != null ? String(options.packages).trim() : '';
|
|
441
|
+
const fromCli = cliPackages.length > 0;
|
|
442
|
+
|
|
443
|
+
const angularRoot = process.cwd();
|
|
444
|
+
const { cide, monorepoRoot } = loadCideConfig(angularRoot);
|
|
445
|
+
|
|
446
|
+
if (!existsSync(path.join(angularRoot, 'cide.json'))) {
|
|
447
|
+
console.warn('No cide.json in current directory — using cwd as both angular and monorepo root.');
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
console.log('\nScanning workspace for publishable packages (global install uses name@version from each package.json)...\n');
|
|
451
|
+
const packages = discoverPublishablePackages({ cide, angularRoot, monorepoRoot });
|
|
452
|
+
if (!packages.length) {
|
|
453
|
+
console.error('No publishable packages found (need publishConfig.registry → GitHub Packages).');
|
|
454
|
+
return;
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
if (!fromCli) {
|
|
458
|
+
printGlobalInstallUsageBox();
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
console.log('Packages:\n');
|
|
462
|
+
packages.forEach((p, i) => {
|
|
463
|
+
const rel = p.labelRel || p.folderName;
|
|
464
|
+
console.log(` [${i + 1}] ${p.name.padEnd(42)} v${p.version} (${rel})`);
|
|
465
|
+
});
|
|
466
|
+
|
|
467
|
+
const ownsReadline = !options.readlineInterface;
|
|
468
|
+
const rl = options.readlineInterface || readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
469
|
+
try {
|
|
470
|
+
const raw = fromCli ? cliPackages : await question(rl, '\nYour choice (see HOW TO above): ');
|
|
471
|
+
const g = parseGlobalInstallSpec(raw, packages.length);
|
|
472
|
+
if (!g.ok) {
|
|
473
|
+
if (g.error) console.error(g.error);
|
|
474
|
+
return;
|
|
475
|
+
}
|
|
476
|
+
if (g.skip || g.indices.length === 0) {
|
|
477
|
+
console.log('\n(No global install.)\n');
|
|
478
|
+
return;
|
|
479
|
+
}
|
|
480
|
+
console.log('\n--- npm install -g ---\n');
|
|
481
|
+
npmInstallGlobalForEntries(packages, g.indices, { force: !!options.force });
|
|
482
|
+
console.log('\n✅ Done.\n');
|
|
483
|
+
} finally {
|
|
484
|
+
if (ownsReadline) rl.close();
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
/**
|
|
489
|
+
* Find every package.json under root (max depth), skip node_modules, .git.
|
|
490
|
+
* @param {object} [opts]
|
|
491
|
+
* @param {string[]} [opts.excludeAncestorDirs] - Absolute dirs: never collect package.json under these (e.g. Angular ng-packagr output).
|
|
492
|
+
*/
|
|
493
|
+
function findConsumerPackageJsonFiles(root, maxDepth = 5, opts = {}) {
|
|
494
|
+
const excludeAncestorDirs = Array.isArray(opts.excludeAncestorDirs)
|
|
495
|
+
? opts.excludeAncestorDirs.map((d) => path.resolve(d))
|
|
496
|
+
: [];
|
|
497
|
+
|
|
498
|
+
function shouldSkipPath(absPath) {
|
|
499
|
+
for (const ex of excludeAncestorDirs) {
|
|
500
|
+
if (isPathInsideOrEqual(absPath, ex)) return true;
|
|
501
|
+
}
|
|
502
|
+
return false;
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
const files = [];
|
|
506
|
+
function walk(dir, depth) {
|
|
507
|
+
if (depth > maxDepth) return;
|
|
508
|
+
const absDir = path.resolve(dir);
|
|
509
|
+
if (shouldSkipPath(absDir)) return;
|
|
510
|
+
let entries;
|
|
511
|
+
try {
|
|
512
|
+
entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
513
|
+
} catch {
|
|
514
|
+
return;
|
|
515
|
+
}
|
|
516
|
+
for (const e of entries) {
|
|
517
|
+
if (e.name === 'node_modules' || e.name === '.git' || e.name === 'A-RND') continue;
|
|
518
|
+
const full = path.join(dir, e.name);
|
|
519
|
+
if (e.isDirectory()) {
|
|
520
|
+
if (e.name === 'dist' && depth > 0) {
|
|
521
|
+
// skip dist subtrees (built output); still allow root-level if any
|
|
522
|
+
continue;
|
|
523
|
+
}
|
|
524
|
+
const absFull = path.resolve(full);
|
|
525
|
+
if (shouldSkipPath(absFull)) continue;
|
|
526
|
+
const pj = path.join(full, 'package.json');
|
|
527
|
+
if (existsSync(pj) && !shouldSkipPath(path.resolve(pj))) files.push(pj);
|
|
528
|
+
walk(full, depth + 1);
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
const rootPj = path.join(root, 'package.json');
|
|
533
|
+
if (existsSync(rootPj)) {
|
|
534
|
+
if (!shouldSkipPath(path.resolve(rootPj))) files.push(rootPj);
|
|
535
|
+
}
|
|
536
|
+
walk(root, 0);
|
|
537
|
+
return [...new Set(files.map((f) => path.resolve(f)))];
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
function shouldReplaceDepVersion(cur) {
|
|
541
|
+
if (typeof cur !== 'string') return false;
|
|
542
|
+
if (cur === '*') return false;
|
|
543
|
+
if (cur.startsWith('workspace:')) return false;
|
|
544
|
+
if (cur.startsWith('file:')) return true;
|
|
545
|
+
if (cur.startsWith('^') || cur.startsWith('~')) return true;
|
|
546
|
+
if (/^\d/.test(cur)) return true;
|
|
547
|
+
if (cur === 'latest') return true;
|
|
548
|
+
return false;
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
function updateDepsSection(section, packageName, newCaret) {
|
|
552
|
+
if (!section || typeof section !== 'object') return false;
|
|
553
|
+
if (!Object.prototype.hasOwnProperty.call(section, packageName)) return false;
|
|
554
|
+
const cur = section[packageName];
|
|
555
|
+
if (!shouldReplaceDepVersion(cur)) return false;
|
|
556
|
+
section[packageName] = newCaret;
|
|
557
|
+
return true;
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
/**
|
|
561
|
+
* Update all workspace package.json files that depend on packageName.
|
|
562
|
+
* excludePackageJsonPaths: published package.json path(s) to skip (consumers only).
|
|
563
|
+
* @param {object} [depOptions]
|
|
564
|
+
* @param {string} [depOptions.distPath] - Relative to angularRoot (default `dist`). Ignores package.json under this tree so ng-packagr output is never treated as a workspace consumer (avoids stale edits and `npm install` duplicating @angular in dist).
|
|
565
|
+
*/
|
|
566
|
+
function updateDependentsInWorkspace(monorepoRoot, angularRoot, packageName, newVersion, excludePackageJsonPaths, depOptions = {}) {
|
|
567
|
+
const newCaret = `^${newVersion}`;
|
|
568
|
+
const exclude = new Set((excludePackageJsonPaths || []).map((p) => path.resolve(p)));
|
|
569
|
+
const distPathRel = depOptions.distPath != null ? depOptions.distPath : 'dist';
|
|
570
|
+
const angularDistAbs = path.resolve(angularRoot, distPathRel);
|
|
571
|
+
const findOpts = { excludeAncestorDirs: [angularDistAbs] };
|
|
572
|
+
|
|
573
|
+
const roots = [...new Set([path.resolve(monorepoRoot), path.resolve(angularRoot)])];
|
|
574
|
+
const allPj = new Set();
|
|
575
|
+
for (const r of roots) {
|
|
576
|
+
for (const f of findConsumerPackageJsonFiles(r, 5, findOpts)) {
|
|
577
|
+
allPj.add(f);
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
const touchedDirs = new Set();
|
|
581
|
+
for (const pjPath of allPj) {
|
|
582
|
+
if (exclude.has(path.resolve(pjPath))) continue;
|
|
583
|
+
let pkg;
|
|
584
|
+
try {
|
|
585
|
+
pkg = readJson(pjPath);
|
|
586
|
+
} catch {
|
|
587
|
+
continue;
|
|
588
|
+
}
|
|
589
|
+
let changed = false;
|
|
590
|
+
if (updateDepsSection(pkg.dependencies, packageName, newCaret)) changed = true;
|
|
591
|
+
if (updateDepsSection(pkg.devDependencies, packageName, newCaret)) changed = true;
|
|
592
|
+
if (updateDepsSection(pkg.peerDependencies, packageName, newCaret)) changed = true;
|
|
593
|
+
if (changed) {
|
|
594
|
+
writeJson(pjPath, pkg);
|
|
595
|
+
touchedDirs.add(path.dirname(pjPath));
|
|
596
|
+
console.log(` ${path.relative(monorepoRoot, pjPath) || path.basename(path.dirname(pjPath))} → ${newCaret}`);
|
|
597
|
+
}
|
|
598
|
+
}
|
|
599
|
+
return [...touchedDirs];
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
function runNpmInstall(dir, npmOpts = {}) {
|
|
603
|
+
const force = npmOpts.force ? ' --force' : '';
|
|
604
|
+
console.log(` npm install in ${dir}${npmOpts.force ? ' (--force)' : ''}`);
|
|
605
|
+
try {
|
|
606
|
+
execSync(`npm install${force}`, { cwd: dir, stdio: 'inherit' });
|
|
607
|
+
} catch (e) {
|
|
608
|
+
console.error(` ⚠ npm install failed in ${dir}:`, e.message);
|
|
609
|
+
}
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
/**
|
|
613
|
+
* @param {object} [options]
|
|
614
|
+
* @param {string} [options.packages] - CLI: -p / --packages
|
|
615
|
+
* @param {string} [options.setVersion] - CLI: exact semver for all selected (when not using idx=ver)
|
|
616
|
+
* @param {string} [options.bump] - CLI: patch | minor | major
|
|
617
|
+
* @param {import('readline').Interface} [options.readlineInterface] - If set (e.g. from `cide shell`), reuse this
|
|
618
|
+
* instead of creating a second readline on stdin — avoids doubled keypresses (ppeettaattcchh).
|
|
619
|
+
* @param {boolean} [options.continueOnError] - If true, on failure log and continue with remaining packages (no prompt).
|
|
620
|
+
* @param {string|number} [options.resumeFrom] - 1-based step in this publish batch to start from (skips earlier steps).
|
|
621
|
+
* @param {boolean} [options.force] - Pass --force to npm install (dependents) and npm install -g (--global).
|
|
622
|
+
* @param {string|number} [options.retryFrom] - Same as --resume-from but skips version bump on that step only (retry failed publish).
|
|
623
|
+
* @param {boolean} [options.noBumpOnResume] - With --resume-from (step > 1): do not bump on the resumed step.
|
|
624
|
+
*/
|
|
625
|
+
async function publishPackage(options = {}) {
|
|
626
|
+
const cliPackages = options.packages != null ? String(options.packages).trim() : '';
|
|
627
|
+
const cliSetVersion = options.setVersion != null ? String(options.setVersion).trim() : '';
|
|
628
|
+
const cliBump = options.bump != null ? String(options.bump).trim().toLowerCase() : 'patch';
|
|
629
|
+
const fromCli = cliPackages.length > 0;
|
|
630
|
+
|
|
631
|
+
const angularRoot = process.cwd();
|
|
632
|
+
const { cide, monorepoRoot } = loadCideConfig(angularRoot);
|
|
633
|
+
|
|
634
|
+
if (!existsSync(path.join(angularRoot, 'cide.json'))) {
|
|
635
|
+
console.warn('No cide.json in current directory — using cwd as both angular and monorepo root.');
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
console.log('\nScanning workspace for publishable packages...\n');
|
|
639
|
+
const packages = discoverPublishablePackages({ cide, angularRoot, monorepoRoot });
|
|
640
|
+
if (!packages.length) {
|
|
641
|
+
console.error('No publishable packages found (need publishConfig.registry → GitHub Packages).');
|
|
642
|
+
console.error('Check cide.json workspace_root / sibling_packages and package publishConfig.');
|
|
643
|
+
return;
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
if (!fromCli) {
|
|
647
|
+
printPublishUsageBox();
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
console.log('Publishable packages:\n');
|
|
651
|
+
packages.forEach((p, i) => {
|
|
652
|
+
const rel = p.labelRel || p.folderName;
|
|
653
|
+
console.log(` [${i + 1}] ${p.name.padEnd(42)} v${p.version} (${rel})`);
|
|
654
|
+
});
|
|
655
|
+
|
|
656
|
+
const ownsReadline = !options.readlineInterface;
|
|
657
|
+
const rl = options.readlineInterface || readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
658
|
+
try {
|
|
659
|
+
let parsed;
|
|
660
|
+
if (fromCli) {
|
|
661
|
+
parsed = parsePackagePublishSpec(cliPackages, packages.length);
|
|
662
|
+
if (!parsed.ok) {
|
|
663
|
+
if (parsed.error !== 'cancelled') console.error(parsed.error || 'Invalid --packages');
|
|
664
|
+
return;
|
|
665
|
+
}
|
|
666
|
+
if (cliSetVersion && parsed.versionByIndex) {
|
|
667
|
+
console.warn('Note: --set-version ignored because --packages uses num=version pairs.');
|
|
668
|
+
}
|
|
669
|
+
} else {
|
|
670
|
+
const raw = await question(rl, '\nYour choice (see HOW TO PUBLISH above): ');
|
|
671
|
+
parsed = parsePackagePublishSpec(raw, packages.length);
|
|
672
|
+
if (!parsed.ok) {
|
|
673
|
+
if (parsed.error !== 'cancelled') console.error(parsed.error || 'Invalid input.');
|
|
674
|
+
console.log('Aborted.');
|
|
675
|
+
return;
|
|
676
|
+
}
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
const { indices, versionByIndex } = parsed;
|
|
680
|
+
|
|
681
|
+
const rawRetryFrom = options.retryFrom != null ? String(options.retryFrom).trim() : '';
|
|
682
|
+
let rawResume = options.resumeFrom != null ? String(options.resumeFrom).trim() : '';
|
|
683
|
+
if (rawRetryFrom !== '') {
|
|
684
|
+
rawResume = rawRetryFrom;
|
|
685
|
+
}
|
|
686
|
+
const usedRetryFromShorthand = rawRetryFrom !== '';
|
|
687
|
+
const noBumpOnResumeFlag = !!options.noBumpOnResume;
|
|
688
|
+
const parsedResume = rawResume === '' ? 1 : parseInt(rawResume, 10);
|
|
689
|
+
const startStep =
|
|
690
|
+
Number.isFinite(parsedResume) && parsedResume >= 1 ? parsedResume : 1;
|
|
691
|
+
if (startStep > indices.length) {
|
|
692
|
+
console.error(
|
|
693
|
+
`--resume-from ${startStep} is past the end of this batch (${indices.length} step(s)). Nothing to do.`
|
|
694
|
+
);
|
|
695
|
+
return;
|
|
696
|
+
}
|
|
697
|
+
if (startStep > 1) {
|
|
698
|
+
const bumpHint =
|
|
699
|
+
usedRetryFromShorthand || (noBumpOnResumeFlag && startStep > 1)
|
|
700
|
+
? ' (version bump skipped for this step — retry)'
|
|
701
|
+
: '';
|
|
702
|
+
console.log(
|
|
703
|
+
`\n(Resuming: skipping batch steps 1..${startStep - 1}, starting at step ${startStep}/${indices.length})${bumpHint}\n`
|
|
704
|
+
);
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
if (noBumpOnResumeFlag && startStep === 1 && !usedRetryFromShorthand) {
|
|
708
|
+
console.warn(
|
|
709
|
+
'Note: --no-bump-on-resume is ignored when starting at step 1 without --retry-from. Use --retry-from 1 to retry the first package without bumping.'
|
|
710
|
+
);
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
const continueOnError = !!options.continueOnError;
|
|
714
|
+
|
|
715
|
+
/** @type {string|null} same semver for every selected index when no per-index map */
|
|
716
|
+
let sharedExactVersion = null;
|
|
717
|
+
/** @type {string} */
|
|
718
|
+
let bumpKind = 'patch';
|
|
719
|
+
|
|
720
|
+
if (versionByIndex) {
|
|
721
|
+
// per-index versions already set
|
|
722
|
+
} else if (fromCli) {
|
|
723
|
+
if (cliSetVersion) {
|
|
724
|
+
if (!isValidSemverString(cliSetVersion)) {
|
|
725
|
+
console.error(`Invalid --set-version "${cliSetVersion}" (need e.g. 2.0.1)`);
|
|
726
|
+
return;
|
|
727
|
+
}
|
|
728
|
+
sharedExactVersion = cliSetVersion;
|
|
729
|
+
} else {
|
|
730
|
+
bumpKind = cliBump === 'major' || cliBump === 'minor' || cliBump === 'patch' ? cliBump : 'patch';
|
|
731
|
+
}
|
|
732
|
+
} else {
|
|
733
|
+
const vAns = (await question(
|
|
734
|
+
rl,
|
|
735
|
+
'Version for ALL selected: type exact semver (e.g. 2.0.1) OR press Enter to bump instead [patch]: '
|
|
736
|
+
)).trim();
|
|
737
|
+
if (isValidSemverString(vAns)) {
|
|
738
|
+
sharedExactVersion = vAns.match(/^\d+\.\d+\.\d+[^\s]*/)[0];
|
|
739
|
+
} else if (vAns !== '') {
|
|
740
|
+
bumpKind = vAns === 'major' || vAns === 'minor' ? vAns : 'patch';
|
|
741
|
+
} else {
|
|
742
|
+
const bumpAns = (await question(rl, 'Bump type? (patch / minor / major) [patch]: ')).trim().toLowerCase();
|
|
743
|
+
bumpKind = bumpAns === 'major' || bumpAns === 'minor' ? bumpAns : 'patch';
|
|
744
|
+
}
|
|
745
|
+
}
|
|
746
|
+
|
|
747
|
+
const distPath = cide.dist_path || 'dist';
|
|
748
|
+
const angularDistAbs = path.resolve(angularRoot, distPath);
|
|
749
|
+
|
|
750
|
+
function resolveNewVersion(entry, idx) {
|
|
751
|
+
if (versionByIndex && versionByIndex.has(idx)) {
|
|
752
|
+
return versionByIndex.get(idx);
|
|
753
|
+
}
|
|
754
|
+
if (sharedExactVersion) {
|
|
755
|
+
return sharedExactVersion;
|
|
756
|
+
}
|
|
757
|
+
return bumpSemver(entry.version, bumpKind);
|
|
758
|
+
}
|
|
759
|
+
|
|
760
|
+
let publishOk = 0;
|
|
761
|
+
let skippedAfterFailure = 0;
|
|
762
|
+
|
|
763
|
+
/** First step after resume: optionally skip bump (retry same step after failure). */
|
|
764
|
+
function shouldSkipBumpThisStep(k) {
|
|
765
|
+
if (k !== startStep - 1) return false;
|
|
766
|
+
if (usedRetryFromShorthand) return true;
|
|
767
|
+
if (noBumpOnResumeFlag && startStep > 1) return true;
|
|
768
|
+
return false;
|
|
769
|
+
}
|
|
770
|
+
|
|
771
|
+
function formatResumeFlagSuffix() {
|
|
772
|
+
const parts = [];
|
|
773
|
+
if (cliSetVersion) parts.push(`--set-version ${cliSetVersion}`);
|
|
774
|
+
else parts.push(`--bump ${cliBump === 'major' || cliBump === 'minor' || cliBump === 'patch' ? cliBump : 'patch'}`);
|
|
775
|
+
if (options.force) parts.push('--force');
|
|
776
|
+
if (options.continueOnError) parts.push('--continue-on-error');
|
|
777
|
+
const g = options.global != null ? String(options.global).trim() : '';
|
|
778
|
+
if (g) parts.push(`--global ${g}`);
|
|
779
|
+
return parts.length ? ` ${parts.join(' ')}` : '';
|
|
780
|
+
}
|
|
781
|
+
|
|
782
|
+
function printStoppedResumeHints(failedStep, nextStep, total) {
|
|
783
|
+
const pSpec =
|
|
784
|
+
fromCli && cliPackages ? cliPackages : '<same -p as before (e.g. all — must match your batch)>';
|
|
785
|
+
const flags = formatResumeFlagSuffix();
|
|
786
|
+
console.log('\nAfter fixing the issue, you can:');
|
|
787
|
+
console.log(
|
|
788
|
+
` • Retry the SAME step (${failedStep}/${total}) without bumping again (version was already set in package.json):`
|
|
789
|
+
);
|
|
790
|
+
console.log(` cide publish -p ${pSpec} --retry-from ${failedStep}${flags}`);
|
|
791
|
+
if (nextStep <= total) {
|
|
792
|
+
console.log(
|
|
793
|
+
` • Or skip the failed package and continue from the NEXT step (${nextStep}/${total}) with a new bump:`
|
|
794
|
+
);
|
|
795
|
+
console.log(` cide publish -p ${pSpec} --resume-from ${nextStep}${flags}`);
|
|
796
|
+
}
|
|
797
|
+
console.log(
|
|
798
|
+
'(Equivalent: --retry-from N = --resume-from N --no-bump-on-resume for that first resumed step.)'
|
|
799
|
+
);
|
|
800
|
+
}
|
|
801
|
+
|
|
802
|
+
for (let k = startStep - 1; k < indices.length; k++) {
|
|
803
|
+
const idx = indices[k];
|
|
804
|
+
const entry = packages[idx];
|
|
805
|
+
const n = k + 1;
|
|
806
|
+
const total = indices.length;
|
|
807
|
+
|
|
808
|
+
/** After an interactive [r]etry, do not bump version again (already written to package.json). */
|
|
809
|
+
let forceNoBump = false;
|
|
810
|
+
let stepComplete = false;
|
|
811
|
+
|
|
812
|
+
while (!stepComplete) {
|
|
813
|
+
console.log(`\n--- Publishing ${n}/${total}: ${entry.name} ---`);
|
|
814
|
+
|
|
815
|
+
let pkg = readJson(entry.packageJsonPath);
|
|
816
|
+
const oldVersion = pkg.version;
|
|
817
|
+
const skipBump = shouldSkipBumpThisStep(k) || forceNoBump;
|
|
818
|
+
let newVersion;
|
|
819
|
+
if (skipBump) {
|
|
820
|
+
newVersion = pkg.version;
|
|
821
|
+
console.log(
|
|
822
|
+
` Version (unchanged${forceNoBump ? ' — retry same step' : ''}): ${newVersion}`
|
|
823
|
+
);
|
|
824
|
+
} else {
|
|
825
|
+
newVersion = resolveNewVersion(entry, idx);
|
|
826
|
+
pkg.version = newVersion;
|
|
827
|
+
writeJson(entry.packageJsonPath, pkg);
|
|
828
|
+
console.log(` Version ${oldVersion} → ${newVersion}`);
|
|
829
|
+
}
|
|
830
|
+
|
|
831
|
+
const originalCwd = process.cwd();
|
|
832
|
+
|
|
833
|
+
try {
|
|
834
|
+
if (entry.kind === 'angular-lib') {
|
|
835
|
+
if (cide.templete !== 'angular') {
|
|
836
|
+
console.warn(' Warning: cide.templete is not "angular"; still running ng build.');
|
|
837
|
+
}
|
|
838
|
+
// entry.workspaceRoot is the Angular workspace that owns this library
|
|
839
|
+
// (may be a sibling workspace, e.g. LMS-UI, not the cwd where cide publish ran).
|
|
840
|
+
const entryWorkspaceRoot = entry.workspaceRoot || angularRoot;
|
|
841
|
+
const ngProjectName = resolveNgProjectNameForPackageDir(
|
|
842
|
+
entryWorkspaceRoot,
|
|
843
|
+
entry.absDir,
|
|
844
|
+
entry.packageJsonPath
|
|
845
|
+
);
|
|
846
|
+
console.log(` Building: ng build --project "${ngProjectName}" (workspace: ${entryWorkspaceRoot})`);
|
|
847
|
+
execSync(`ng build --project "${ngProjectName}"`, { cwd: entryWorkspaceRoot, stdio: 'inherit', shell: true });
|
|
848
|
+
|
|
849
|
+
const distDir = resolveAngularDistDir(entryWorkspaceRoot, distPath, entry);
|
|
850
|
+
if (!existsSync(distDir)) {
|
|
851
|
+
throw new Error(`Dist folder not found: ${distDir}`);
|
|
852
|
+
}
|
|
853
|
+
console.log(' Publishing from dist...');
|
|
854
|
+
execSync('npm publish', { cwd: distDir, stdio: 'inherit' });
|
|
855
|
+
} else {
|
|
856
|
+
if (entry.hasBuildScript) {
|
|
857
|
+
console.log(' Building: npm run build');
|
|
858
|
+
execSync('npm run build', { cwd: entry.absDir, stdio: 'inherit' });
|
|
859
|
+
} else {
|
|
860
|
+
console.log(' Skipping build (no build script).');
|
|
861
|
+
}
|
|
862
|
+
console.log(' Publishing from package root...');
|
|
863
|
+
execSync('npm publish', { cwd: entry.absDir, stdio: 'inherit' });
|
|
864
|
+
}
|
|
865
|
+
|
|
866
|
+
console.log(' Updating dependents:');
|
|
867
|
+
const touched = updateDependentsInWorkspace(
|
|
868
|
+
monorepoRoot,
|
|
869
|
+
angularRoot,
|
|
870
|
+
entry.name,
|
|
871
|
+
newVersion,
|
|
872
|
+
[entry.packageJsonPath],
|
|
873
|
+
{ distPath }
|
|
874
|
+
);
|
|
875
|
+
let rootsToInstall = new Set();
|
|
876
|
+
for (const d of touched) {
|
|
877
|
+
if (isPathInsideOrEqual(d, angularDistAbs)) {
|
|
878
|
+
console.log(` (skip npm install in Angular build output: ${d})`);
|
|
879
|
+
continue;
|
|
880
|
+
}
|
|
881
|
+
|
|
882
|
+
// In Angular workspaces, library dependencies are satisfied by the root node_modules.
|
|
883
|
+
// Installing inside projects/xyz creates unwanted local node_modules.
|
|
884
|
+
const absD = path.resolve(d);
|
|
885
|
+
const absRoot = path.resolve(angularRoot);
|
|
886
|
+
if (absD !== absRoot && isPathInsideOrEqual(absD, absRoot)) {
|
|
887
|
+
console.log(` (skip npm install in library folder: ${d} — will install at workspace root instead)`);
|
|
888
|
+
rootsToInstall.add(absRoot);
|
|
889
|
+
} else {
|
|
890
|
+
rootsToInstall.add(absD);
|
|
891
|
+
}
|
|
892
|
+
}
|
|
893
|
+
|
|
894
|
+
for (const rootToInstall of rootsToInstall) {
|
|
895
|
+
runNpmInstall(rootToInstall, { force: !!options.force });
|
|
896
|
+
}
|
|
897
|
+
if (touched.length === 0) {
|
|
898
|
+
console.log(' (no other package.json references updated)');
|
|
899
|
+
}
|
|
900
|
+
publishOk++;
|
|
901
|
+
stepComplete = true;
|
|
902
|
+
} catch (err) {
|
|
903
|
+
console.error(` ❌ Failed: ${err.message}`);
|
|
904
|
+
process.chdir(originalCwd);
|
|
905
|
+
|
|
906
|
+
const nextStep = k + 2;
|
|
907
|
+
const canAsk =
|
|
908
|
+
rl &&
|
|
909
|
+
(process.stdin.isTTY || options.readlineInterface) &&
|
|
910
|
+
!continueOnError;
|
|
911
|
+
|
|
912
|
+
if (continueOnError) {
|
|
913
|
+
skippedAfterFailure++;
|
|
914
|
+
console.log(' (--continue-on-error: continuing with remaining packages.)');
|
|
915
|
+
stepComplete = true;
|
|
916
|
+
} else if (!canAsk) {
|
|
917
|
+
console.error('\nStopped.');
|
|
918
|
+
console.error(
|
|
919
|
+
'(Non-interactive: add --continue-on-error to keep going, or use --retry-from for the failed step / --resume-from for the next.)'
|
|
920
|
+
);
|
|
921
|
+
printStoppedResumeHints(n, nextStep, total);
|
|
922
|
+
throw err;
|
|
923
|
+
} else {
|
|
924
|
+
const ans = await question(
|
|
925
|
+
rl,
|
|
926
|
+
'\n[y] skip to next package / [r]etry this step (re-run build & publish; no extra bump) / [N] stop: '
|
|
927
|
+
);
|
|
928
|
+
const a = String(ans).trim().toLowerCase();
|
|
929
|
+
if (a === 'r' || a === 'retry') {
|
|
930
|
+
forceNoBump = true;
|
|
931
|
+
console.log(`\n ↻ Retrying step ${n}/${total} (${entry.name})...\n`);
|
|
932
|
+
continue;
|
|
933
|
+
} else if (/^y(es)?$/i.test(a)) {
|
|
934
|
+
skippedAfterFailure++;
|
|
935
|
+
console.log('\n (Leaving failed package as-is; continuing with the rest.)');
|
|
936
|
+
stepComplete = true;
|
|
937
|
+
} else {
|
|
938
|
+
console.log('\nStopped.');
|
|
939
|
+
printStoppedResumeHints(n, nextStep, total);
|
|
940
|
+
return;
|
|
941
|
+
}
|
|
942
|
+
}
|
|
943
|
+
} finally {
|
|
944
|
+
process.chdir(originalCwd);
|
|
945
|
+
}
|
|
946
|
+
}
|
|
947
|
+
}
|
|
948
|
+
|
|
949
|
+
const batchSteps = indices.length - (startStep - 1);
|
|
950
|
+
if (skippedAfterFailure === 0) {
|
|
951
|
+
console.log(`\n✅ Published ${publishOk} package(s) successfully.\n`);
|
|
952
|
+
} else {
|
|
953
|
+
console.log(
|
|
954
|
+
`\n⚠️ Batch finished: ${publishOk} published OK, ${skippedAfterFailure} failed (skipped; ${batchSteps} step(s) in this run).\n`
|
|
955
|
+
);
|
|
956
|
+
}
|
|
957
|
+
|
|
958
|
+
await runOptionalGlobalInstall(rl, packages, options);
|
|
959
|
+
} finally {
|
|
960
|
+
if (ownsReadline) rl.close();
|
|
961
|
+
}
|
|
962
|
+
}
|
|
963
|
+
|
|
964
|
+
publishPackage.loadCideConfig = loadCideConfig;
|
|
965
|
+
publishPackage.discoverPublishablePackages = discoverPublishablePackages;
|
|
966
|
+
publishPackage.installGlobalPackage = installGlobalPackage;
|
|
967
|
+
publishPackage.parsePackagePublishSpec = parsePackagePublishSpec;
|
|
968
|
+
|
|
969
|
+
module.exports = publishPackage;
|