@git.zone/tsdocker 1.13.0 → 1.15.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/dist_ts/00_commitinfo_data.js +1 -1
- package/dist_ts/classes.dockerfile.d.ts +13 -0
- package/dist_ts/classes.dockerfile.js +106 -19
- package/dist_ts/classes.tsdockermanager.js +95 -31
- package/dist_ts/interfaces/index.d.ts +2 -0
- package/dist_ts/tsdocker.cli.js +162 -18
- package/dist_ts/tsdocker.plugins.d.ts +2 -1
- package/dist_ts/tsdocker.plugins.js +3 -2
- package/package.json +2 -1
- package/readme.hints.md +12 -0
- package/ts/00_commitinfo_data.ts +1 -1
- package/ts/classes.dockerfile.ts +118 -21
- package/ts/classes.tsdockermanager.ts +98 -30
- package/ts/interfaces/index.ts +2 -0
- package/ts/tsdocker.cli.ts +208 -16
- package/ts/tsdocker.plugins.ts +2 -0
|
@@ -167,6 +167,16 @@ export class TsDockerManager {
|
|
|
167
167
|
logger.log('info', 'Cache: disabled (--no-cache)');
|
|
168
168
|
}
|
|
169
169
|
|
|
170
|
+
if (options?.parallel) {
|
|
171
|
+
const concurrency = options.parallelConcurrency ?? 4;
|
|
172
|
+
const levels = Dockerfile.computeLevels(toBuild);
|
|
173
|
+
logger.log('info', `Parallel build: ${levels.length} level(s), concurrency ${concurrency}`);
|
|
174
|
+
for (let l = 0; l < levels.length; l++) {
|
|
175
|
+
const level = levels[l];
|
|
176
|
+
logger.log('info', ` Level ${l} (${level.length}): ${level.map(df => df.cleanTag).join(', ')}`);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
170
180
|
logger.log('info', `Building ${toBuild.length} Dockerfile(s)...`);
|
|
171
181
|
|
|
172
182
|
if (options?.cached) {
|
|
@@ -184,41 +194,97 @@ export class TsDockerManager {
|
|
|
184
194
|
}
|
|
185
195
|
|
|
186
196
|
try {
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
const
|
|
190
|
-
const
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
logger.log('info',
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
197
|
+
if (options?.parallel) {
|
|
198
|
+
// === PARALLEL CACHED MODE ===
|
|
199
|
+
const concurrency = options.parallelConcurrency ?? 4;
|
|
200
|
+
const levels = Dockerfile.computeLevels(toBuild);
|
|
201
|
+
|
|
202
|
+
let built = 0;
|
|
203
|
+
for (let l = 0; l < levels.length; l++) {
|
|
204
|
+
const level = levels[l];
|
|
205
|
+
logger.log('info', `--- Level ${l}: building ${level.length} image(s) in parallel ---`);
|
|
206
|
+
|
|
207
|
+
const tasks = level.map((df) => {
|
|
208
|
+
const myIndex = ++built;
|
|
209
|
+
return async () => {
|
|
210
|
+
const progress = `(${myIndex}/${total})`;
|
|
211
|
+
const skip = await cache.shouldSkipBuild(df.cleanTag, df.content);
|
|
212
|
+
|
|
213
|
+
if (skip) {
|
|
214
|
+
logger.log('ok', `${progress} Skipped ${df.cleanTag} (cached)`);
|
|
215
|
+
} else {
|
|
216
|
+
logger.log('info', `${progress} Building ${df.cleanTag}...`);
|
|
217
|
+
const elapsed = await df.build({
|
|
218
|
+
platform: options?.platform,
|
|
219
|
+
timeout: options?.timeout,
|
|
220
|
+
noCache: options?.noCache,
|
|
221
|
+
verbose: options?.verbose,
|
|
222
|
+
});
|
|
223
|
+
logger.log('ok', `${progress} Built ${df.cleanTag} in ${formatDuration(elapsed)}`);
|
|
224
|
+
const imageId = await df.getId();
|
|
225
|
+
cache.recordBuild(df.cleanTag, df.content, imageId, df.buildTag);
|
|
226
|
+
}
|
|
227
|
+
return df;
|
|
228
|
+
};
|
|
201
229
|
});
|
|
202
|
-
logger.log('ok', `${progress} Built ${dockerfileArg.cleanTag} in ${formatDuration(elapsed)}`);
|
|
203
|
-
const imageId = await dockerfileArg.getId();
|
|
204
|
-
cache.recordBuild(dockerfileArg.cleanTag, dockerfileArg.content, imageId, dockerfileArg.buildTag);
|
|
205
|
-
}
|
|
206
230
|
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
dependentBaseImages
|
|
231
|
+
await Dockerfile.runWithConcurrency(tasks, concurrency);
|
|
232
|
+
|
|
233
|
+
// After the entire level completes, tag + push for dependency resolution
|
|
234
|
+
for (const df of level) {
|
|
235
|
+
const dependentBaseImages = new Set<string>();
|
|
236
|
+
for (const other of toBuild) {
|
|
237
|
+
if (other.localBaseDockerfile === df && other.baseImage !== df.buildTag) {
|
|
238
|
+
dependentBaseImages.add(other.baseImage);
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
for (const fullTag of dependentBaseImages) {
|
|
242
|
+
logger.log('info', `Tagging ${df.buildTag} as ${fullTag} for local dependency resolution`);
|
|
243
|
+
await smartshellInstance.exec(`docker tag ${df.buildTag} ${fullTag}`);
|
|
244
|
+
}
|
|
245
|
+
if (useRegistry && toBuild.some(other => other.localBaseDockerfile === df)) {
|
|
246
|
+
await Dockerfile.pushToLocalRegistry(df);
|
|
247
|
+
}
|
|
212
248
|
}
|
|
213
249
|
}
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
250
|
+
} else {
|
|
251
|
+
// === SEQUENTIAL CACHED MODE ===
|
|
252
|
+
for (let i = 0; i < total; i++) {
|
|
253
|
+
const dockerfileArg = toBuild[i];
|
|
254
|
+
const progress = `(${i + 1}/${total})`;
|
|
255
|
+
const skip = await cache.shouldSkipBuild(dockerfileArg.cleanTag, dockerfileArg.content);
|
|
256
|
+
|
|
257
|
+
if (skip) {
|
|
258
|
+
logger.log('ok', `${progress} Skipped ${dockerfileArg.cleanTag} (cached)`);
|
|
259
|
+
} else {
|
|
260
|
+
logger.log('info', `${progress} Building ${dockerfileArg.cleanTag}...`);
|
|
261
|
+
const elapsed = await dockerfileArg.build({
|
|
262
|
+
platform: options?.platform,
|
|
263
|
+
timeout: options?.timeout,
|
|
264
|
+
noCache: options?.noCache,
|
|
265
|
+
verbose: options?.verbose,
|
|
266
|
+
});
|
|
267
|
+
logger.log('ok', `${progress} Built ${dockerfileArg.cleanTag} in ${formatDuration(elapsed)}`);
|
|
268
|
+
const imageId = await dockerfileArg.getId();
|
|
269
|
+
cache.recordBuild(dockerfileArg.cleanTag, dockerfileArg.content, imageId, dockerfileArg.buildTag);
|
|
270
|
+
}
|
|
218
271
|
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
272
|
+
// Tag for dependents IMMEDIATELY (not after all builds)
|
|
273
|
+
const dependentBaseImages = new Set<string>();
|
|
274
|
+
for (const other of toBuild) {
|
|
275
|
+
if (other.localBaseDockerfile === dockerfileArg && other.baseImage !== dockerfileArg.buildTag) {
|
|
276
|
+
dependentBaseImages.add(other.baseImage);
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
for (const fullTag of dependentBaseImages) {
|
|
280
|
+
logger.log('info', `Tagging ${dockerfileArg.buildTag} as ${fullTag} for local dependency resolution`);
|
|
281
|
+
await smartshellInstance.exec(`docker tag ${dockerfileArg.buildTag} ${fullTag}`);
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
// Push to local registry for buildx (even for cache hits — image exists but registry doesn't)
|
|
285
|
+
if (useRegistry && toBuild.some(other => other.localBaseDockerfile === dockerfileArg)) {
|
|
286
|
+
await Dockerfile.pushToLocalRegistry(dockerfileArg);
|
|
287
|
+
}
|
|
222
288
|
}
|
|
223
289
|
}
|
|
224
290
|
} finally {
|
|
@@ -237,6 +303,8 @@ export class TsDockerManager {
|
|
|
237
303
|
noCache: options?.noCache,
|
|
238
304
|
verbose: options?.verbose,
|
|
239
305
|
isRootless: this.dockerContext.contextInfo?.isRootless,
|
|
306
|
+
parallel: options?.parallel,
|
|
307
|
+
parallelConcurrency: options?.parallelConcurrency,
|
|
240
308
|
});
|
|
241
309
|
}
|
|
242
310
|
|
package/ts/interfaces/index.ts
CHANGED
|
@@ -80,6 +80,8 @@ export interface IBuildCommandOptions {
|
|
|
80
80
|
cached?: boolean; // Skip builds when Dockerfile content hasn't changed
|
|
81
81
|
verbose?: boolean; // Stream raw docker build output (default: silent)
|
|
82
82
|
context?: string; // Explicit Docker context name (--context flag)
|
|
83
|
+
parallel?: boolean; // Enable parallel builds within dependency levels
|
|
84
|
+
parallelConcurrency?: number; // Max concurrent builds per level (default 4)
|
|
83
85
|
}
|
|
84
86
|
|
|
85
87
|
export interface ICacheEntry {
|
package/ts/tsdocker.cli.ts
CHANGED
|
@@ -7,6 +7,7 @@ import * as DockerModule from './tsdocker.docker.js';
|
|
|
7
7
|
|
|
8
8
|
import { logger, ora } from './tsdocker.logging.js';
|
|
9
9
|
import { TsDockerManager } from './classes.tsdockermanager.js';
|
|
10
|
+
import { DockerContext } from './classes.dockercontext.js';
|
|
10
11
|
import type { IBuildCommandOptions } from './interfaces/index.js';
|
|
11
12
|
import { commitinfo } from './00_commitinfo_data.js';
|
|
12
13
|
|
|
@@ -55,6 +56,12 @@ export let run = () => {
|
|
|
55
56
|
if (argvArg.verbose) {
|
|
56
57
|
buildOptions.verbose = true;
|
|
57
58
|
}
|
|
59
|
+
if (argvArg.parallel) {
|
|
60
|
+
buildOptions.parallel = true;
|
|
61
|
+
if (typeof argvArg.parallel === 'number') {
|
|
62
|
+
buildOptions.parallelConcurrency = argvArg.parallel;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
58
65
|
|
|
59
66
|
await manager.build(buildOptions);
|
|
60
67
|
logger.log('success', 'Build completed successfully');
|
|
@@ -95,6 +102,12 @@ export let run = () => {
|
|
|
95
102
|
if (argvArg.verbose) {
|
|
96
103
|
buildOptions.verbose = true;
|
|
97
104
|
}
|
|
105
|
+
if (argvArg.parallel) {
|
|
106
|
+
buildOptions.parallel = true;
|
|
107
|
+
if (typeof argvArg.parallel === 'number') {
|
|
108
|
+
buildOptions.parallelConcurrency = argvArg.parallel;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
98
111
|
|
|
99
112
|
// Build images first (if not already built)
|
|
100
113
|
await manager.build(buildOptions);
|
|
@@ -157,6 +170,12 @@ export let run = () => {
|
|
|
157
170
|
if (argvArg.verbose) {
|
|
158
171
|
buildOptions.verbose = true;
|
|
159
172
|
}
|
|
173
|
+
if (argvArg.parallel) {
|
|
174
|
+
buildOptions.parallel = true;
|
|
175
|
+
if (typeof argvArg.parallel === 'number') {
|
|
176
|
+
buildOptions.parallelConcurrency = argvArg.parallel;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
160
179
|
await manager.build(buildOptions);
|
|
161
180
|
|
|
162
181
|
// Run tests
|
|
@@ -218,27 +237,200 @@ export let run = () => {
|
|
|
218
237
|
});
|
|
219
238
|
|
|
220
239
|
tsdockerCli.addCommand('clean').subscribe(async argvArg => {
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
const
|
|
224
|
-
|
|
225
|
-
});
|
|
226
|
-
|
|
227
|
-
|
|
240
|
+
try {
|
|
241
|
+
const autoYes = !!argvArg.y;
|
|
242
|
+
const includeAll = !!argvArg.all;
|
|
243
|
+
|
|
244
|
+
const smartshellInstance = new plugins.smartshell.Smartshell({ executor: 'bash' });
|
|
245
|
+
const interact = new plugins.smartinteract.SmartInteract();
|
|
246
|
+
|
|
247
|
+
// --- Docker context detection ---
|
|
248
|
+
ora.text('detecting docker context...');
|
|
249
|
+
const dockerContext = new DockerContext();
|
|
250
|
+
if (argvArg.context) {
|
|
251
|
+
dockerContext.setContext(argvArg.context as string);
|
|
252
|
+
}
|
|
253
|
+
await dockerContext.detect();
|
|
254
|
+
ora.stop();
|
|
255
|
+
dockerContext.logContextInfo();
|
|
256
|
+
|
|
257
|
+
// --- Helper: parse docker output into resource list ---
|
|
258
|
+
interface IDockerResource {
|
|
259
|
+
id: string;
|
|
260
|
+
display: string;
|
|
261
|
+
}
|
|
228
262
|
|
|
229
|
-
|
|
230
|
-
|
|
263
|
+
const listResources = async (command: string): Promise<IDockerResource[]> => {
|
|
264
|
+
const result = await smartshellInstance.execSilent(command);
|
|
265
|
+
if (result.exitCode !== 0 || !result.stdout.trim()) {
|
|
266
|
+
return [];
|
|
267
|
+
}
|
|
268
|
+
return result.stdout.trim().split('\n').filter(Boolean).map((line) => {
|
|
269
|
+
const parts = line.split('\t');
|
|
270
|
+
return {
|
|
271
|
+
id: parts[0],
|
|
272
|
+
display: parts.join(' | '),
|
|
273
|
+
};
|
|
274
|
+
});
|
|
275
|
+
};
|
|
231
276
|
|
|
232
|
-
|
|
233
|
-
|
|
277
|
+
// --- Helper: checkbox selection ---
|
|
278
|
+
const selectResources = async (
|
|
279
|
+
name: string,
|
|
280
|
+
message: string,
|
|
281
|
+
resources: IDockerResource[],
|
|
282
|
+
): Promise<string[]> => {
|
|
283
|
+
if (autoYes) {
|
|
284
|
+
return resources.map((r) => r.id);
|
|
285
|
+
}
|
|
286
|
+
const answer = await interact.askQuestion({
|
|
287
|
+
name,
|
|
288
|
+
type: 'checkbox',
|
|
289
|
+
message,
|
|
290
|
+
default: [],
|
|
291
|
+
choices: resources.map((r) => ({ name: r.display, value: r.id })),
|
|
292
|
+
});
|
|
293
|
+
return answer.value as string[];
|
|
294
|
+
};
|
|
234
295
|
|
|
235
|
-
|
|
236
|
-
|
|
296
|
+
// --- Helper: confirm action ---
|
|
297
|
+
const confirmAction = async (
|
|
298
|
+
name: string,
|
|
299
|
+
message: string,
|
|
300
|
+
): Promise<boolean> => {
|
|
301
|
+
if (autoYes) {
|
|
302
|
+
return true;
|
|
303
|
+
}
|
|
304
|
+
const answer = await interact.askQuestion({
|
|
305
|
+
name,
|
|
306
|
+
type: 'confirm',
|
|
307
|
+
message,
|
|
308
|
+
default: false,
|
|
309
|
+
});
|
|
310
|
+
return answer.value as boolean;
|
|
311
|
+
};
|
|
237
312
|
|
|
238
|
-
|
|
239
|
-
|
|
313
|
+
// === RUNNING CONTAINERS ===
|
|
314
|
+
const runningContainers = await listResources(
|
|
315
|
+
`docker ps --format '{{.ID}}\t{{.Names}}\t{{.Image}}\t{{.Status}}'`
|
|
316
|
+
);
|
|
317
|
+
if (runningContainers.length > 0) {
|
|
318
|
+
logger.log('info', `Found ${runningContainers.length} running container(s)`);
|
|
319
|
+
const selectedIds = await selectResources(
|
|
320
|
+
'runningContainers',
|
|
321
|
+
'Select running containers to kill:',
|
|
322
|
+
runningContainers,
|
|
323
|
+
);
|
|
324
|
+
if (selectedIds.length > 0) {
|
|
325
|
+
logger.log('info', `Killing ${selectedIds.length} container(s)...`);
|
|
326
|
+
await smartshellInstance.exec(`docker kill ${selectedIds.join(' ')}`);
|
|
327
|
+
}
|
|
328
|
+
} else {
|
|
329
|
+
logger.log('info', 'No running containers found');
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
// === STOPPED CONTAINERS ===
|
|
333
|
+
const stoppedContainers = await listResources(
|
|
334
|
+
`docker ps -a --filter status=exited --filter status=created --format '{{.ID}}\t{{.Names}}\t{{.Image}}\t{{.Status}}'`
|
|
335
|
+
);
|
|
336
|
+
if (stoppedContainers.length > 0) {
|
|
337
|
+
logger.log('info', `Found ${stoppedContainers.length} stopped container(s)`);
|
|
338
|
+
const selectedIds = await selectResources(
|
|
339
|
+
'stoppedContainers',
|
|
340
|
+
'Select stopped containers to remove:',
|
|
341
|
+
stoppedContainers,
|
|
342
|
+
);
|
|
343
|
+
if (selectedIds.length > 0) {
|
|
344
|
+
logger.log('info', `Removing ${selectedIds.length} container(s)...`);
|
|
345
|
+
await smartshellInstance.exec(`docker rm ${selectedIds.join(' ')}`);
|
|
346
|
+
}
|
|
347
|
+
} else {
|
|
348
|
+
logger.log('info', 'No stopped containers found');
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
// === DANGLING IMAGES ===
|
|
352
|
+
const danglingImages = await listResources(
|
|
353
|
+
`docker images -f dangling=true --format '{{.ID}}\t{{.Repository}}:{{.Tag}}\t{{.Size}}'`
|
|
354
|
+
);
|
|
355
|
+
if (danglingImages.length > 0) {
|
|
356
|
+
const confirmed = await confirmAction(
|
|
357
|
+
'removeDanglingImages',
|
|
358
|
+
`Remove ${danglingImages.length} dangling image(s)?`,
|
|
359
|
+
);
|
|
360
|
+
if (confirmed) {
|
|
361
|
+
logger.log('info', `Removing ${danglingImages.length} dangling image(s)...`);
|
|
362
|
+
const ids = danglingImages.map((r) => r.id).join(' ');
|
|
363
|
+
await smartshellInstance.exec(`docker rmi ${ids}`);
|
|
364
|
+
}
|
|
365
|
+
} else {
|
|
366
|
+
logger.log('info', 'No dangling images found');
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
// === ALL IMAGES (only with --all) ===
|
|
370
|
+
if (includeAll) {
|
|
371
|
+
const allImages = await listResources(
|
|
372
|
+
`docker images --format '{{.ID}}\t{{.Repository}}:{{.Tag}}\t{{.Size}}'`
|
|
373
|
+
);
|
|
374
|
+
if (allImages.length > 0) {
|
|
375
|
+
logger.log('info', `Found ${allImages.length} image(s) total`);
|
|
376
|
+
const selectedIds = await selectResources(
|
|
377
|
+
'allImages',
|
|
378
|
+
'Select images to remove:',
|
|
379
|
+
allImages,
|
|
380
|
+
);
|
|
381
|
+
if (selectedIds.length > 0) {
|
|
382
|
+
logger.log('info', `Removing ${selectedIds.length} image(s)...`);
|
|
383
|
+
await smartshellInstance.exec(`docker rmi -f ${selectedIds.join(' ')}`);
|
|
384
|
+
}
|
|
385
|
+
} else {
|
|
386
|
+
logger.log('info', 'No images found');
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
// === DANGLING VOLUMES ===
|
|
391
|
+
const danglingVolumes = await listResources(
|
|
392
|
+
`docker volume ls -f dangling=true --format '{{.Name}}\t{{.Driver}}'`
|
|
393
|
+
);
|
|
394
|
+
if (danglingVolumes.length > 0) {
|
|
395
|
+
const confirmed = await confirmAction(
|
|
396
|
+
'removeDanglingVolumes',
|
|
397
|
+
`Remove ${danglingVolumes.length} dangling volume(s)?`,
|
|
398
|
+
);
|
|
399
|
+
if (confirmed) {
|
|
400
|
+
logger.log('info', `Removing ${danglingVolumes.length} dangling volume(s)...`);
|
|
401
|
+
const names = danglingVolumes.map((r) => r.id).join(' ');
|
|
402
|
+
await smartshellInstance.exec(`docker volume rm ${names}`);
|
|
403
|
+
}
|
|
404
|
+
} else {
|
|
405
|
+
logger.log('info', 'No dangling volumes found');
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
// === ALL VOLUMES (only with --all) ===
|
|
409
|
+
if (includeAll) {
|
|
410
|
+
const allVolumes = await listResources(
|
|
411
|
+
`docker volume ls --format '{{.Name}}\t{{.Driver}}'`
|
|
412
|
+
);
|
|
413
|
+
if (allVolumes.length > 0) {
|
|
414
|
+
logger.log('info', `Found ${allVolumes.length} volume(s) total`);
|
|
415
|
+
const selectedIds = await selectResources(
|
|
416
|
+
'allVolumes',
|
|
417
|
+
'Select volumes to remove:',
|
|
418
|
+
allVolumes,
|
|
419
|
+
);
|
|
420
|
+
if (selectedIds.length > 0) {
|
|
421
|
+
logger.log('info', `Removing ${selectedIds.length} volume(s)...`);
|
|
422
|
+
await smartshellInstance.exec(`docker volume rm ${selectedIds.join(' ')}`);
|
|
423
|
+
}
|
|
424
|
+
} else {
|
|
425
|
+
logger.log('info', 'No volumes found');
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
logger.log('success', 'Docker cleanup completed!');
|
|
430
|
+
} catch (err) {
|
|
431
|
+
logger.log('error', `Clean failed: ${(err as Error).message}`);
|
|
432
|
+
process.exit(1);
|
|
240
433
|
}
|
|
241
|
-
ora.finishSuccess('docker environment now is clean!');
|
|
242
434
|
});
|
|
243
435
|
|
|
244
436
|
tsdockerCli.addCommand('vscode').subscribe(async argvArg => {
|
package/ts/tsdocker.plugins.ts
CHANGED
|
@@ -11,6 +11,7 @@ import * as smartlog from '@push.rocks/smartlog';
|
|
|
11
11
|
import * as smartlogDestinationLocal from '@push.rocks/smartlog-destination-local';
|
|
12
12
|
import * as smartlogSouceOra from '@push.rocks/smartlog-source-ora';
|
|
13
13
|
import * as smartopen from '@push.rocks/smartopen';
|
|
14
|
+
import * as smartinteract from '@push.rocks/smartinteract';
|
|
14
15
|
import * as smartshell from '@push.rocks/smartshell';
|
|
15
16
|
import * as smartstring from '@push.rocks/smartstring';
|
|
16
17
|
|
|
@@ -25,6 +26,7 @@ export {
|
|
|
25
26
|
smartpromise,
|
|
26
27
|
qenv,
|
|
27
28
|
smartcli,
|
|
29
|
+
smartinteract,
|
|
28
30
|
smartlog,
|
|
29
31
|
smartlogDestinationLocal,
|
|
30
32
|
smartlogSouceOra,
|