arcane-os 0.95.0 → 0.96.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 +18 -0
- package/README.md +1 -1
- package/browser-runtime/ai/core-model-assets.mjs +74 -0
- package/docs/reference/README.md +9 -0
- package/docs/reference/ai/browser-speech.md +61 -10
- package/docs/reference/core-client.md +35 -0
- package/docs/reference/core-native-packaging.md +38 -6
- package/docs/reference/inventory/package-api.json +133 -5
- package/docs/reference/inventory/runtime-modules.json +39 -13
- package/docs/reference/model-assets.md +164 -3
- package/docs/reference/native-speech.md +105 -4
- package/docs/reference/runtime-modules.md +157 -11
- package/docs/reference/sdk-api.md +247 -4
- package/package.json +3 -3
- package/src/core/host.mjs +1 -0
- package/src/core/services/local-ai.mjs +46 -12
- package/src/core/services/model-assets.mjs +491 -59
- package/src/core/services/speech.mjs +2 -2
- package/src/local-ai/nemo-models.mjs +160 -0
- package/src/local-ai/nemotron/index.mjs +63 -7
- package/src/native/macos-provider.mjs +14 -7
- package/src/native/portable-assembly.mjs +180 -0
- package/src/native/portable-provider.mjs +17 -161
- package/src/native/publish-output.mjs +101 -0
- package/src/native/windows-provider.mjs +12 -6
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {readFile, rm, stat} from 'node:fs/promises';
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import Is from 'strong-type';
|
|
4
|
-
import {createCoreClassicSource} from '../core-classic-source.mjs';
|
|
5
4
|
import {ArcaneError, ERROR_CODES, throwIfAborted} from '../errors.mjs';
|
|
6
|
-
import {SDK_ROOT, CORE_FILES, outputFile
|
|
5
|
+
import {SDK_ROOT, CORE_FILES, outputFile} from './portable-layout.mjs';
|
|
6
|
+
import {assemblePortable} from './portable-assembly.mjs';
|
|
7
|
+
import {publishNativeOutput} from './publish-output.mjs';
|
|
7
8
|
|
|
8
9
|
const is = new Is(false);
|
|
9
10
|
const PROTOCOL = 'arcane-native-builder/1';
|
|
@@ -20,66 +21,6 @@ async function emit(onEvent, event) {
|
|
|
20
21
|
if (is.function(onEvent)) await onEvent(event);
|
|
21
22
|
}
|
|
22
23
|
|
|
23
|
-
function contains(root, candidate) {
|
|
24
|
-
const relative = path.relative(root, candidate);
|
|
25
|
-
return relative === '' || (!path.isAbsolute(relative) && relative !== '..' && !relative.startsWith(`..${path.sep}`));
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
async function futureRealPath(location) {
|
|
29
|
-
try { return await realpath(location); }
|
|
30
|
-
catch (error) {
|
|
31
|
-
if (error.code !== 'ENOENT') throw error;
|
|
32
|
-
const parent = path.dirname(location);
|
|
33
|
-
if (parent === location) throw error;
|
|
34
|
-
return path.join(await futureRealPath(parent), path.basename(location));
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
async function outputDirectory(request) {
|
|
39
|
-
if (!is.string(request.outputRoot) || !request.outputRoot) {
|
|
40
|
-
throw new ArcaneError(ERROR_CODES.usage, 'Portable assembly requires an outputRoot.');
|
|
41
|
-
}
|
|
42
|
-
const output = await futureRealPath(path.resolve(request.outputRoot));
|
|
43
|
-
const protectedRoots = [SDK_ROOT, request.appReleaseRoot,
|
|
44
|
-
...(request.dependencies?.map(function dependencyRoot(item) { return item.releaseRoot; }) ?? []),
|
|
45
|
-
...(request.protectedRoots ?? [])];
|
|
46
|
-
for (const location of protectedRoots) {
|
|
47
|
-
if (!location) continue;
|
|
48
|
-
const root = await futureRealPath(path.resolve(location));
|
|
49
|
-
// SDK/release roots must never be replaced by generated output. A
|
|
50
|
-
// caller may select its ordinary project-local dist descendant.
|
|
51
|
-
if (contains(output, root) || (location !== SDK_ROOT && contains(root, output))) {
|
|
52
|
-
throw new ArcaneError(ERROR_CODES.usage, `Portable output overlaps selected source: ${location}.`);
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
await mkdir(output, {recursive: true});
|
|
56
|
-
return output;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
function serviceSelection(services, release) {
|
|
60
|
-
return services.map(function selectService(service) {
|
|
61
|
-
if (!is.string(service?.module) || !release.files.includes(service.module)) {
|
|
62
|
-
throw new ArcaneError(ERROR_CODES.prerequisiteMissing, `The selected release does not contain service module ${String(service?.module)}.`);
|
|
63
|
-
}
|
|
64
|
-
return {...service, options: service.options === undefined ? {} : service.options};
|
|
65
|
-
});
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
function eventOwnerPath(release) {
|
|
69
|
-
const paths = release.files.filter(function sdkEventModule(relative) {
|
|
70
|
-
return relative === 'sdk/event-manager.mjs' || relative.endsWith('/sdk/event-manager.mjs')
|
|
71
|
-
|| /^node_modules\/(?:@[^/]+\/)?[^/]+\/browser-runtime\/event-manager\.mjs$/u.test(relative);
|
|
72
|
-
});
|
|
73
|
-
if (paths.length !== 1) {
|
|
74
|
-
throw new ArcaneError(ERROR_CODES.prerequisiteMissing,
|
|
75
|
-
'The portable app release must select one shared SDK event-manager module.',
|
|
76
|
-
{details: {paths}});
|
|
77
|
-
}
|
|
78
|
-
// Native hosts mount the complete app directory at one local URL origin.
|
|
79
|
-
// An origin-root URL keeps nested navigable documents on the same owner.
|
|
80
|
-
return '/' + paths[0].split('/').map(encodeURIComponent).join('/');
|
|
81
|
-
}
|
|
82
|
-
|
|
83
24
|
/** Package-owned assembly; never imports an OS checkout or application service. */
|
|
84
25
|
export function createPortableNativeProvider({services, packagedWeb = false, webKitDocumentLifecycle = false} = {}) {
|
|
85
26
|
return {
|
|
@@ -117,108 +58,23 @@ export function createPortableNativeProvider({services, packagedWeb = false, web
|
|
|
117
58
|
return result;
|
|
118
59
|
},
|
|
119
60
|
async build(request = {}) {
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
const
|
|
123
|
-
const
|
|
124
|
-
const selectedServices = serviceSelection(services ?? appDescriptor.native?.services ?? [], release);
|
|
125
|
-
const eventOwnerModuleURL = eventOwnerPath(release);
|
|
126
|
-
const output = await outputDirectory(request);
|
|
127
|
-
throwIfAborted(signal);
|
|
128
|
-
// Unique assembly directories preserve earlier outputs and avoid
|
|
129
|
-
// overwriting an app's files or a concurrent packaging operation.
|
|
130
|
-
const root = await mkdtemp(path.join(output, 'arcane-portable-'));
|
|
131
|
-
const files = [];
|
|
61
|
+
throwIfAborted(request.signal);
|
|
62
|
+
portableTarget(request.targetRequest);
|
|
63
|
+
const artifact = await assemblePortable(request, {services, packagedWeb, webKitDocumentLifecycle});
|
|
64
|
+
const sourceRoot = artifact.target.rootDir;
|
|
132
65
|
try {
|
|
133
|
-
await
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
runtimes: localAI.runtimes,
|
|
139
|
-
directory: path.join(output, 'local-ai-runtimes'),
|
|
140
|
-
outputRoot: root,
|
|
141
|
-
platform: target.platform === 'windows' ? 'win32' : target.platform === 'macos' ? 'darwin' : target.platform,
|
|
142
|
-
architecture: target.architecture,
|
|
143
|
-
signal,
|
|
144
|
-
onEvent
|
|
145
|
-
});
|
|
146
|
-
}
|
|
147
|
-
// These outputs have separate directories. Join both owners
|
|
148
|
-
// before cleanup so neither can keep writing a removed assembly.
|
|
149
|
-
const preparation = await Promise.allSettled([
|
|
150
|
-
copyRelease({...request, releaseRoot: request.appReleaseRoot}, 'app', root, files, signal),
|
|
151
|
-
bundleSelectedRuntimes()
|
|
152
|
-
]);
|
|
153
|
-
const failures = preparation.filter(function failedPreparation(result) { return result.status === 'rejected'; });
|
|
154
|
-
if (failures.length === 1) throw failures[0].reason;
|
|
155
|
-
if (failures.length > 1) {
|
|
156
|
-
throw new AggregateError(failures.map(function preparationError(result) { return result.reason; }),
|
|
157
|
-
'Portable application and local runtime preparation failed.');
|
|
158
|
-
}
|
|
159
|
-
const bundled = preparation[1].value;
|
|
160
|
-
files.push(...bundled.files);
|
|
161
|
-
const dependencies = [];
|
|
162
|
-
for (const [index, dependency] of (request.dependencies ?? []).entries()) {
|
|
163
|
-
const directory = `dependencies/${index}`;
|
|
164
|
-
await copyRelease(dependency, directory, root, files, signal);
|
|
165
|
-
dependencies.push({appId: dependency.appId, root: directory, release: dependency.release});
|
|
166
|
-
}
|
|
167
|
-
const sdk = await copyCoreRuntime(root, files, signal);
|
|
168
|
-
const selectedSdk = request.selectedSdk;
|
|
169
|
-
let appSdk;
|
|
170
|
-
if (selectedSdk && release.files.some(function selectedPackageFile(relative) {
|
|
171
|
-
return relative.startsWith(`${selectedSdk.packageSource}/`);
|
|
172
|
-
})) {
|
|
173
|
-
const destination = `app/${selectedSdk.packageSource}`;
|
|
174
|
-
let metadata;
|
|
175
|
-
if (release.files.includes(`${selectedSdk.packageSource}/package.json`)) {
|
|
176
|
-
metadata = JSON.parse(await readFile(outputFile(root, `${destination}/package.json`), 'utf8'));
|
|
177
|
-
} else {
|
|
178
|
-
metadata = await copyCoreRuntime(root, files, signal, {
|
|
179
|
-
sourceRoot: selectedSdk.packageRoot, sdkDestination: destination
|
|
180
|
-
});
|
|
66
|
+
const root = await publishNativeOutput({
|
|
67
|
+
sourceRoot, outputRoot: path.dirname(sourceRoot), appId: artifact.app.id,
|
|
68
|
+
signal: request.signal,
|
|
69
|
+
async onPublished(outputRoot) {
|
|
70
|
+
await emit(request.onEvent, {type: 'native.payload.completed', appId: artifact.app.id, outputRoot});
|
|
181
71
|
}
|
|
182
|
-
|
|
183
|
-
}
|
|
184
|
-
await writeOutput(root, 'package.json', '{"type":"module"}\n', files, signal);
|
|
185
|
-
await writeOutput(root, 'runtime/arcane-core.mjs', coreEntrySource(appDescriptor, sdk.version, selectedServices,
|
|
186
|
-
{localAI, runtimes: bundled.runtimes, packagedWeb}), files, signal);
|
|
187
|
-
await writeOutput(root, 'runtime/arcane-api.js', await createCoreClassicSource({
|
|
188
|
-
eventOwnerModuleURL, replayRuntimeState: true, webKitDocumentLifecycle
|
|
189
|
-
}), files, signal);
|
|
190
|
-
const manifest = {
|
|
191
|
-
schemaVersion: 1,
|
|
192
|
-
kind: 'arcane-portable-native',
|
|
193
|
-
sdk: {name: sdk.name, version: sdk.version},
|
|
194
|
-
...(appSdk ? {appSdk} : {}),
|
|
195
|
-
app: appDescriptor,
|
|
196
|
-
target,
|
|
197
|
-
webRoot: 'app',
|
|
198
|
-
start: release.manifest?.app?.start ?? './index.html',
|
|
199
|
-
...(appDescriptor.native?.window === undefined ? {} : {window: {...appDescriptor.native.window}}),
|
|
200
|
-
...(appDescriptor.native?.launchContext === undefined ? {} : {launchContext: appDescriptor.native.launchContext}),
|
|
201
|
-
core: {
|
|
202
|
-
entry: 'runtime/arcane-core.mjs', transport: 'stdio', protocol: 'arcane/1', services: selectedServices,
|
|
203
|
-
...(localAI === undefined ? {} : {localAIRuntimes: bundled.runtimes})
|
|
204
|
-
},
|
|
205
|
-
client: {
|
|
206
|
-
source: 'runtime/arcane-api.js', injection: 'document-created', eventOwnerModuleURL, replayRuntimeState: true,
|
|
207
|
-
...(webKitDocumentLifecycle ? {webKitDocumentLifecycle: true} : {})
|
|
208
|
-
},
|
|
209
|
-
dependencies,
|
|
210
|
-
minimumCoreVersion: request.minimumCoreVersion ?? null,
|
|
211
|
-
files: [...files, MANIFEST]
|
|
212
|
-
};
|
|
213
|
-
await writeOutput(root, MANIFEST, JSON.stringify(manifest, null, 2) + '\n', files, signal);
|
|
214
|
-
throwIfAborted(signal);
|
|
215
|
-
await emit(onEvent, {type: 'native.payload.completed', appId: appDescriptor.id, outputRoot: root});
|
|
216
|
-
throwIfAborted(signal);
|
|
217
|
-
return {app: appDescriptor, target: {...target, rootDir: root}, manifest};
|
|
72
|
+
});
|
|
73
|
+
return {...artifact, target: {...artifact.target, rootDir: root}};
|
|
218
74
|
} catch (error) {
|
|
219
|
-
try { await rm(
|
|
75
|
+
try { await rm(sourceRoot, {recursive: true, force: true}); }
|
|
220
76
|
catch (cleanupError) {
|
|
221
|
-
throw new AggregateError([error, cleanupError], `Portable
|
|
77
|
+
throw new AggregateError([error, cleanupError], `Portable publication failed; its assembly remains at ${sourceRoot}.`);
|
|
222
78
|
}
|
|
223
79
|
throw error;
|
|
224
80
|
}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import {lstat, mkdir, readFile, rename, rm, writeFile} from 'node:fs/promises';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import {ArcaneError, ERROR_CODES, throwIfAborted} from '../errors.mjs';
|
|
4
|
+
import {outputFile} from './portable-layout.mjs';
|
|
5
|
+
|
|
6
|
+
/** Publish only a complete native artifact, retaining the previous app until acceptance. */
|
|
7
|
+
export async function publishNativeOutput({sourceRoot, outputRoot, appId, bundle = false, signal, onPublished}) {
|
|
8
|
+
const parent = path.resolve(outputRoot);
|
|
9
|
+
const source = path.resolve(sourceRoot);
|
|
10
|
+
const name = bundle ? `${appId}.app` : appId;
|
|
11
|
+
// Keep valid application IDs usable on Windows without changing their
|
|
12
|
+
// descriptor identity, including when another platform assembles the app.
|
|
13
|
+
const outputName = /^(?:con|prn|aux|nul|com[1-9¹²³]|lpt[1-9¹²³])(?:\.|$)/iu.test(name) ? `_${name}` : name;
|
|
14
|
+
const destination = outputFile(parent, outputName);
|
|
15
|
+
if (path.dirname(destination) !== parent || source === destination) {
|
|
16
|
+
throw new ArcaneError(ERROR_CODES.usage, 'Native publication requires a separate app-specific output child.');
|
|
17
|
+
}
|
|
18
|
+
const reservation = path.join(parent, `.arcane-publish-${path.basename(destination)}`);
|
|
19
|
+
const backup = path.join(reservation, 'previous');
|
|
20
|
+
const state = {sourceRoot: source, outputRoot: destination, reservation, backup, promoted: false, committed: false};
|
|
21
|
+
throwIfAborted(signal);
|
|
22
|
+
try { await mkdir(reservation); }
|
|
23
|
+
catch (error) {
|
|
24
|
+
if (error.code !== 'EEXIST') throw error;
|
|
25
|
+
throw new ArcaneError(ERROR_CODES.operationFailed,
|
|
26
|
+
`Native output publication is already reserved at ${reservation}. Finish or recover that publication before retrying.`,
|
|
27
|
+
{cause: error, details: state});
|
|
28
|
+
}
|
|
29
|
+
let previous = false;
|
|
30
|
+
let failure;
|
|
31
|
+
let retainReservation = false;
|
|
32
|
+
try {
|
|
33
|
+
// This short filesystem reservation protects only final replacement,
|
|
34
|
+
// including across processes. Assembly and other apps stay independent.
|
|
35
|
+
const acquiredAt = new Date();
|
|
36
|
+
const ttlMs = 5 * 60 * 1000;
|
|
37
|
+
await writeFile(path.join(reservation, 'owner.json'), JSON.stringify({
|
|
38
|
+
pid: process.pid, acquiredAt: acquiredAt.toISOString(), expiresAt: new Date(acquiredAt.getTime() + ttlMs).toISOString(), ttlMs,
|
|
39
|
+
sourceRoot: source, outputRoot: destination, reservation, backup,
|
|
40
|
+
recovery: 'Expiry is informational and never permits automatic takeover. After the owning build has stopped, inspect both outputs. Restore previous if publication did not finish; preserve the current output if it did. Remove this reservation only after recovery.'
|
|
41
|
+
}, null, 2) + '\n', {flag: 'wx'});
|
|
42
|
+
throwIfAborted(signal);
|
|
43
|
+
let existing;
|
|
44
|
+
try { existing = await lstat(destination); }
|
|
45
|
+
catch (error) { if (error.code !== 'ENOENT') throw error; }
|
|
46
|
+
if (existing) {
|
|
47
|
+
// Replacement is scoped to this app's generated artifact, never a
|
|
48
|
+
// cache, source directory, linked folder or unrelated user data.
|
|
49
|
+
if (!existing.isDirectory() || existing.isSymbolicLink()) {
|
|
50
|
+
throw new ArcaneError(ERROR_CODES.usage, `Native output is not a replaceable app directory: ${destination}.`);
|
|
51
|
+
}
|
|
52
|
+
const manifestRoot = bundle ? path.join(destination, 'Contents', 'Resources') : destination;
|
|
53
|
+
const manifest = JSON.parse(await readFile(path.join(manifestRoot, 'arcane-native.json'), 'utf8'));
|
|
54
|
+
if (manifest.app?.id !== appId || !['arcane-portable-native', 'arcane-windows-native', 'arcane-macos-native'].includes(manifest.kind)) {
|
|
55
|
+
throw new ArcaneError(ERROR_CODES.usage, `Native output belongs to a different artifact: ${destination}.`);
|
|
56
|
+
}
|
|
57
|
+
throwIfAborted(signal);
|
|
58
|
+
await rename(destination, backup);
|
|
59
|
+
previous = true;
|
|
60
|
+
}
|
|
61
|
+
throwIfAborted(signal);
|
|
62
|
+
await rename(source, destination);
|
|
63
|
+
state.promoted = true;
|
|
64
|
+
throwIfAborted(signal);
|
|
65
|
+
// Completion observers see the actual final paths. Their ordinary
|
|
66
|
+
// rejection/cancellation still rolls back before the build is accepted.
|
|
67
|
+
await onPublished?.(destination);
|
|
68
|
+
throwIfAborted(signal);
|
|
69
|
+
state.committed = true;
|
|
70
|
+
} catch (error) {
|
|
71
|
+
const failures = [error];
|
|
72
|
+
if (state.promoted) {
|
|
73
|
+
try {
|
|
74
|
+
await rename(destination, source);
|
|
75
|
+
state.promoted = false;
|
|
76
|
+
} catch (restoreError) { failures.push(restoreError); }
|
|
77
|
+
}
|
|
78
|
+
if (previous && !state.promoted) {
|
|
79
|
+
try {
|
|
80
|
+
await rename(backup, destination);
|
|
81
|
+
previous = false;
|
|
82
|
+
} catch (restoreError) { failures.push(restoreError); }
|
|
83
|
+
}
|
|
84
|
+
retainReservation = failures.length > 1;
|
|
85
|
+
failure = retainReservation
|
|
86
|
+
? new ArcaneError(ERROR_CODES.operationFailed,
|
|
87
|
+
`Native output recovery is incomplete. Preserve and inspect ${destination}, ${source} and ${reservation}.`,
|
|
88
|
+
{cause: new AggregateError(failures, 'Native publication and recovery failed.'), details: state})
|
|
89
|
+
: error;
|
|
90
|
+
}
|
|
91
|
+
if (!retainReservation) {
|
|
92
|
+
try { await rm(reservation, {recursive: true, force: true}); }
|
|
93
|
+
catch (cleanupError) {
|
|
94
|
+
failure = new ArcaneError(ERROR_CODES.operationFailed,
|
|
95
|
+
`Native output ${state.committed ? 'was published' : 'was not accepted'}, and publication cleanup failed at ${reservation}.`,
|
|
96
|
+
{cause: failure ? new AggregateError([failure, cleanupError], 'Native publication cleanup failed.') : cleanupError, details: state});
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
if (failure) throw failure;
|
|
100
|
+
return destination;
|
|
101
|
+
}
|
|
@@ -7,6 +7,8 @@ import Is from 'strong-type';
|
|
|
7
7
|
import {ArcaneError, ERROR_CODES, throwIfAborted} from '../errors.mjs';
|
|
8
8
|
import {runProcess} from '../process.mjs';
|
|
9
9
|
import {createPortableNativeProvider} from './portable-provider.mjs';
|
|
10
|
+
import {assemblePortable} from './portable-assembly.mjs';
|
|
11
|
+
import {publishNativeOutput} from './publish-output.mjs';
|
|
10
12
|
import {outputFile, writeOutput} from './portable-layout.mjs';
|
|
11
13
|
import {createWindowsApplicationIcon} from './windows-icon.mjs';
|
|
12
14
|
|
|
@@ -275,13 +277,13 @@ export function createWindowsNativeProvider({services, hostDirectory} = {}) {
|
|
|
275
277
|
const target = windowsTarget(request.targetRequest);
|
|
276
278
|
const selected = hostDirectory ?? request.toolchain?.hostDirectory;
|
|
277
279
|
const selectedHost = selected === undefined ? null : await requireHostDirectory(selected);
|
|
278
|
-
const artifact = await
|
|
280
|
+
const artifact = await assemblePortable({
|
|
279
281
|
...request,
|
|
280
282
|
targetRequest: portableRequest(target),
|
|
281
283
|
async onEvent(event) {
|
|
282
|
-
|
|
284
|
+
await emit(onEvent, {...event, target: 'windows-x64'});
|
|
283
285
|
}
|
|
284
|
-
});
|
|
286
|
+
}, {services});
|
|
285
287
|
const root = artifact.target.rootDir;
|
|
286
288
|
try {
|
|
287
289
|
const host = selectedHost ?? await releasedHostDirectory(path.resolve(request.outputRoot), artifact.manifest.sdk.version, {signal, onEvent});
|
|
@@ -304,9 +306,13 @@ export function createWindowsNativeProvider({services, hostDirectory} = {}) {
|
|
|
304
306
|
};
|
|
305
307
|
await writeFile(path.join(root, MANIFEST), `${JSON.stringify(manifest, null, 2)}\n`, {signal});
|
|
306
308
|
throwIfAborted(signal);
|
|
307
|
-
await
|
|
308
|
-
|
|
309
|
-
|
|
309
|
+
const output = await publishNativeOutput({
|
|
310
|
+
sourceRoot: root, outputRoot: path.dirname(root), appId: request.appDescriptor.id, signal,
|
|
311
|
+
async onPublished(outputRoot) {
|
|
312
|
+
await emit(onEvent, {type: 'native.payload.completed', target: 'windows-x64', appId: request.appDescriptor.id, outputRoot});
|
|
313
|
+
}
|
|
314
|
+
});
|
|
315
|
+
return {...artifact, target: {...target, rootDir: output}, manifest};
|
|
310
316
|
} catch (error) {
|
|
311
317
|
return removeOwnedDirectory(root, error);
|
|
312
318
|
}
|