naro-cli 0.15.0 → 0.16.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/README.md +35 -4
- package/dist/{chunk-6WJ3QQ5Y.js → chunk-XNT7SINN.js} +111 -29
- package/dist/deploy.d.ts +4 -1
- package/dist/deploy.js +1 -1
- package/dist/index.js +410 -89
- package/dist/output.d.ts +5 -0
- package/dist/upload.d.ts +2 -0
- package/package.json +4 -4
package/README.md
CHANGED
|
@@ -180,7 +180,7 @@ naro storage bucket update avatars --private | --file-size-limit none | --allowe
|
|
|
180
180
|
naro storage bucket update docs --public (asks first: anyone with an object's URL can download it)
|
|
181
181
|
naro storage bucket list | empty <name> | remove <name> [--empty] (empty and remove ask first)
|
|
182
182
|
naro storage ls naro://avatars/u1 [--recursive]
|
|
183
|
-
naro storage cp ./me.png naro://avatars/u1/me.png [--upsert] Up to 50 MiB, with the service key
|
|
183
|
+
naro storage cp ./me.png naro://avatars/u1/me.png [--upsert] Up to the object limit, under 4 GiB (over 50 MiB in parts), with the service key
|
|
184
184
|
naro storage cp naro://docs/team-1/plan.pdf ./ Replaces a local file of the same name
|
|
185
185
|
naro storage rm naro://avatars/u1/old.png [--recursive] (asks first)
|
|
186
186
|
naro storage rm naro://avatars/u1/ --recursive Everything under u1/; written u1, an object u1 too
|
|
@@ -793,13 +793,43 @@ An `--update-check` you give replaces the default: without
|
|
|
793
793
|
|
|
794
794
|
## Release notes
|
|
795
795
|
|
|
796
|
+
### 0.16.0
|
|
797
|
+
|
|
798
|
+
- `naro storage cp` uploads a file up to the object limit, under 4 GiB. Over
|
|
799
|
+
50 MiB it goes in parts, four at a time, and a part that fails is sent again
|
|
800
|
+
(up to three more times) before the upload stops. The 50 MiB refusal before
|
|
801
|
+
any request is gone: the worker judges the size when the upload starts, and a
|
|
802
|
+
file over the bucket's or the plan's limit answers `413 OBJECT_TOO_LARGE`
|
|
803
|
+
before a byte is sent. An upload in parts must finish within 24 hours of its
|
|
804
|
+
start (R2 ends unfinished multipart uploads after a day). A big upload prints its progress on stderr: one line
|
|
805
|
+
rewritten in place at a terminal, a line every 10% otherwise
|
|
806
|
+
(`↑ 312 MiB / 1.2 GiB (26%)`), and nothing with `--json`.
|
|
807
|
+
- Ctrl-C during an upload (or a TERM, or the terminal closing) stops it:
|
|
808
|
+
`Stopping the upload…` on stderr, the upload is called off — one in parts on
|
|
809
|
+
the backend too, so its name and the space it reserved are free at once
|
|
810
|
+
rather than locked for about an hour — and the run ends with the signal. A
|
|
811
|
+
second Ctrl-C ends it at once, without waiting for the call-off. A process
|
|
812
|
+
killed outright, or a network lost, still leaves the name locked until the
|
|
813
|
+
lock runs out.
|
|
814
|
+
- A file of 4 GiB or more is refused before any request
|
|
815
|
+
(`VALIDATION_FAILED`): Node reads its size modulo 4 GiB (4 GiB + 1 KiB reads
|
|
816
|
+
as 1 KiB), and the upload would send only that much and report success.
|
|
817
|
+
Uploading one needs a change in naro-js that is not there yet.
|
|
818
|
+
- A download is written to disk as it arrives, to a hidden `.naro-partial`
|
|
819
|
+
file that replaces the destination once it is whole, instead of being held in
|
|
820
|
+
memory first.
|
|
821
|
+
- `naro storage` says `50 MiB per request (larger files go in parts)`, and
|
|
822
|
+
that uploads stopped over the storage limit reopen usually within an hour and
|
|
823
|
+
a half: R2 reports the bucket's size 30 minutes to about an hour and a half
|
|
824
|
+
late.
|
|
825
|
+
|
|
796
826
|
### 0.15.0
|
|
797
827
|
|
|
798
828
|
- `naro storage` shows the Storage meter's day between `Used:` and `Buckets:`:
|
|
799
829
|
`Today (UTC):` has the write operations (R2 Class A) against the daily limit
|
|
800
830
|
and the reads, `Measured:` the bucket size R2 reported and when R2 measured
|
|
801
|
-
it (R2 reports the size 30
|
|
802
|
-
minutes). A `✗` line
|
|
831
|
+
it (R2 reports the size 30 minutes to about an hour and a half late, and the
|
|
832
|
+
meter reads it every 15 minutes). A `✗` line
|
|
803
833
|
says uploads are stopped, over the storage limit until objects are deleted
|
|
804
834
|
or over the day's write operations until 00:00 UTC (downloads still work);
|
|
805
835
|
a `⚠` line warns from 80% of the storage limit or of the day's write
|
|
@@ -872,7 +902,8 @@ An `--update-check` you give replaces the default: without
|
|
|
872
902
|
(public or private, the largest file, the types taken), and `ls`, `cp`
|
|
873
903
|
and `rm` work on `naro://<bucket>/<path>` with the project's service key
|
|
874
904
|
(`NARO_SERVICE_KEY`, or fetched with a warning) — the bytes go to the
|
|
875
|
-
project's backend, not through naro. One
|
|
905
|
+
project's backend, not through naro. One request carries 50 MiB at most; a
|
|
906
|
+
larger file goes in parts since 0.16.0 (under 4 GiB).
|
|
876
907
|
`bucket update --public`, `empty`, `remove` and `rm` ask first; without a
|
|
877
908
|
terminal they need `--yes` (`CONFIRMATION_REQUIRED` otherwise, nothing
|
|
878
909
|
sent).
|
|
@@ -26,6 +26,7 @@ function artifactUrlKey(kind, name) {
|
|
|
26
26
|
return `${kind}/${name}`;
|
|
27
27
|
}
|
|
28
28
|
var ARTIFACT_URL_BATCH_SIZE = 100;
|
|
29
|
+
var ARTIFACT_UPLOAD_CONCURRENCY = 8;
|
|
29
30
|
function parseArtifactPutUrls(payload) {
|
|
30
31
|
if (typeof payload !== "object" || payload === null) return null;
|
|
31
32
|
const { urls, disabled } = payload;
|
|
@@ -37,28 +38,55 @@ function parseArtifactPutUrls(payload) {
|
|
|
37
38
|
}
|
|
38
39
|
return resolved;
|
|
39
40
|
}
|
|
41
|
+
async function eachWithLimit(items, limit, work) {
|
|
42
|
+
let next = 0;
|
|
43
|
+
const lanes = Array.from(
|
|
44
|
+
{ length: Math.min(limit, items.length) },
|
|
45
|
+
async () => {
|
|
46
|
+
while (next < items.length) {
|
|
47
|
+
const item = items[next];
|
|
48
|
+
next += 1;
|
|
49
|
+
await work(item);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
);
|
|
53
|
+
await Promise.all(lanes);
|
|
54
|
+
}
|
|
40
55
|
async function uploadArtifactFiles(artifact, ports) {
|
|
41
56
|
const entries = artifactUploadEntries(artifact);
|
|
42
57
|
let proxied = 0;
|
|
43
58
|
for (let index = 0; index < entries.length; index += ARTIFACT_URL_BATCH_SIZE) {
|
|
44
59
|
const batch = entries.slice(index, index + ARTIFACT_URL_BATCH_SIZE);
|
|
45
60
|
const urls = await ports.resolvePutUrls(batch);
|
|
46
|
-
|
|
61
|
+
const viaProxy = [];
|
|
62
|
+
await eachWithLimit(batch, ARTIFACT_UPLOAD_CONCURRENCY, async (entry) => {
|
|
47
63
|
const url2 = urls?.get(artifactUrlKey(entry.kind, entry.file.name));
|
|
48
64
|
if (!url2) {
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
continue;
|
|
65
|
+
viaProxy.push(entry);
|
|
66
|
+
return;
|
|
52
67
|
}
|
|
53
68
|
try {
|
|
54
69
|
await ports.putSigned(url2, entry);
|
|
55
70
|
} catch {
|
|
56
|
-
|
|
57
|
-
await ports.putProxied(entry);
|
|
71
|
+
viaProxy.push(entry);
|
|
58
72
|
}
|
|
73
|
+
});
|
|
74
|
+
for (const entry of viaProxy) {
|
|
75
|
+
proxied += 1;
|
|
76
|
+
await ports.putProxied(entry);
|
|
59
77
|
}
|
|
60
78
|
}
|
|
61
|
-
|
|
79
|
+
const bytes = entries.reduce(
|
|
80
|
+
(total, entry) => total + entry.file.data.byteLength,
|
|
81
|
+
0
|
|
82
|
+
);
|
|
83
|
+
return { proxied, total: entries.length, bytes };
|
|
84
|
+
}
|
|
85
|
+
function artifactUploadSummary(report, elapsedMs) {
|
|
86
|
+
const kilobytes = Math.ceil(report.bytes / 1e3);
|
|
87
|
+
const size = kilobytes >= 1e3 ? `${(report.bytes / 1e6).toFixed(1)} MB` : `${String(kilobytes)} kB`;
|
|
88
|
+
const files = report.total === 1 ? "file" : "files";
|
|
89
|
+
return `Uploaded ${String(report.total)} artifact ${files} (${size}) in ${(elapsedMs / 1e3).toFixed(1)} s.`;
|
|
62
90
|
}
|
|
63
91
|
|
|
64
92
|
// ../../packages/build/src/cache.ts
|
|
@@ -100,6 +128,7 @@ async function reportUnavailable(scope, ports, observer) {
|
|
|
100
128
|
"Build cache is unavailable - this build runs uncached and saves nothing for the next one."
|
|
101
129
|
);
|
|
102
130
|
}
|
|
131
|
+
var UNKNOWN_PAX_KEYWORD = /^tar: Ignoring unknown extended header keyword /;
|
|
103
132
|
async function restoreCache(args) {
|
|
104
133
|
const { scope, key, targetDir, ports, observer } = args;
|
|
105
134
|
const urls = await ports.transport.resolveUrls(scope, key);
|
|
@@ -132,7 +161,9 @@ async function restoreCache(args) {
|
|
|
132
161
|
stage: STAGE[scope],
|
|
133
162
|
errorCode: ERROR_CODE[scope]
|
|
134
163
|
},
|
|
135
|
-
|
|
164
|
+
{
|
|
165
|
+
onLog: (entry) => UNKNOWN_PAX_KEYWORD.test(entry.message) ? void 0 : observer.onLog(entry)
|
|
166
|
+
}
|
|
136
167
|
);
|
|
137
168
|
await log(observer, scope, "INFO", `Restored ${scope} cache (${key}).`);
|
|
138
169
|
return true;
|
|
@@ -160,7 +191,14 @@ async function saveCache(args) {
|
|
|
160
191
|
await ports.runCommand(
|
|
161
192
|
{
|
|
162
193
|
command: "tar",
|
|
163
|
-
|
|
194
|
+
// --no-xattrs: macOS bsdtar would store extended attributes that the
|
|
195
|
+
// build container's GNU tar warns about per file (UNKNOWN_PAX_KEYWORD).
|
|
196
|
+
// GNU tar 1.27+ and bsdtar 3.x both take it.
|
|
197
|
+
args: ["--no-xattrs", "--zstd", "-cf", archive, "-C", sourceDir, "."],
|
|
198
|
+
// macOS can also write the same metadata as AppleDouble `._name` files,
|
|
199
|
+
// which GNU tar restores as ordinary files (PR #114 review; not seen on
|
|
200
|
+
// bsdtar 3.5.3 here). GNU tar ignores the variable.
|
|
201
|
+
env: { COPYFILE_DISABLE: "1" },
|
|
164
202
|
cwd: sourceDir,
|
|
165
203
|
timeoutMs: 12e4,
|
|
166
204
|
stage: STAGE[scope],
|
|
@@ -257,6 +295,9 @@ var PHASES = [
|
|
|
257
295
|
];
|
|
258
296
|
var ACTIVE_STATUSES = new Set(PHASES);
|
|
259
297
|
|
|
298
|
+
// ../../packages/core/src/deployment-stall.ts
|
|
299
|
+
var STALL_WINDOW_MS = 10 * 6e4;
|
|
300
|
+
|
|
260
301
|
// ../../packages/core/src/deployment-strategy.ts
|
|
261
302
|
var MIN_SUPPORTED_NEXT_MAJOR = 15;
|
|
262
303
|
|
|
@@ -15254,7 +15295,8 @@ function cacheEnv(dir) {
|
|
|
15254
15295
|
return {
|
|
15255
15296
|
npm_config_store_dir: dir,
|
|
15256
15297
|
npm_config_cache: dir,
|
|
15257
|
-
YARN_CACHE_FOLDER: dir
|
|
15298
|
+
YARN_CACHE_FOLDER: dir,
|
|
15299
|
+
YARN_ENABLE_GLOBAL_CACHE: "0"
|
|
15258
15300
|
};
|
|
15259
15301
|
}
|
|
15260
15302
|
function withSharedCache(spec, dir) {
|
|
@@ -15313,7 +15355,8 @@ async function detectPackageManager(root) {
|
|
|
15313
15355
|
"install",
|
|
15314
15356
|
modern ? "--immutable" : "--frozen-lockfile"
|
|
15315
15357
|
],
|
|
15316
|
-
// Berry takes no install-time cache flag;
|
|
15358
|
+
// Berry takes no install-time cache flag; cacheEnv covers it
|
|
15359
|
+
// (YARN_CACHE_FOLDER with the global cache switched off).
|
|
15317
15360
|
cacheArgs: (dir) => modern ? [] : ["--cache-folder", dir],
|
|
15318
15361
|
addArgs: (specs) => ["yarn", "add", ...specs]
|
|
15319
15362
|
};
|
|
@@ -18446,22 +18489,31 @@ function createLogBatcher(options) {
|
|
|
18446
18489
|
let buffer = [];
|
|
18447
18490
|
let timer = null;
|
|
18448
18491
|
let pending = null;
|
|
18492
|
+
let sending = Promise.resolve();
|
|
18449
18493
|
function disarm() {
|
|
18450
18494
|
if (timer === null) return;
|
|
18451
18495
|
clearTimeout(timer);
|
|
18452
18496
|
timer = null;
|
|
18453
18497
|
}
|
|
18498
|
+
function surfacePending() {
|
|
18499
|
+
if (pending === null) return;
|
|
18500
|
+
const { error: error51 } = pending;
|
|
18501
|
+
pending = null;
|
|
18502
|
+
throw error51;
|
|
18503
|
+
}
|
|
18454
18504
|
async function flush() {
|
|
18455
18505
|
disarm();
|
|
18456
|
-
|
|
18457
|
-
|
|
18458
|
-
|
|
18459
|
-
|
|
18506
|
+
surfacePending();
|
|
18507
|
+
if (buffer.length === 0) {
|
|
18508
|
+
await sending;
|
|
18509
|
+
surfacePending();
|
|
18510
|
+
return;
|
|
18460
18511
|
}
|
|
18461
|
-
if (buffer.length === 0) return;
|
|
18462
18512
|
const batch = buffer;
|
|
18463
18513
|
buffer = [];
|
|
18464
|
-
|
|
18514
|
+
const send = sending.then(() => options.send(batch));
|
|
18515
|
+
sending = send.catch(() => void 0);
|
|
18516
|
+
await send;
|
|
18465
18517
|
}
|
|
18466
18518
|
function arm() {
|
|
18467
18519
|
if (timer !== null || !options.intervalMs) return;
|
|
@@ -21080,6 +21132,10 @@ var CATALOG = {
|
|
|
21080
21132
|
status: 404,
|
|
21081
21133
|
message: ({ bucket, path }) => `Object ${storageName(path)} was not found in bucket ${storageName(bucket)}.`
|
|
21082
21134
|
},
|
|
21135
|
+
UPLOAD_NOT_FOUND: {
|
|
21136
|
+
status: 404,
|
|
21137
|
+
message: ({ upload }) => `Upload ${storageName(upload)} was not found or has expired.`
|
|
21138
|
+
},
|
|
21083
21139
|
INVALID_CONTENT_TYPE: {
|
|
21084
21140
|
status: 400,
|
|
21085
21141
|
message: ({ type }) => `Content-Type ${storageName(type)} is not a media type like image/png.`
|
|
@@ -21094,7 +21150,7 @@ var CATALOG = {
|
|
|
21094
21150
|
},
|
|
21095
21151
|
OBJECT_TOO_LARGE: {
|
|
21096
21152
|
status: 413,
|
|
21097
|
-
message: (vars) => "single" in vars ? "Uploads over 50 MiB
|
|
21153
|
+
message: (vars) => "single" in vars ? "Uploads over 50 MiB go in parts: use the multipart upload." : `The object is ${String(vars.size)} bytes; bucket ${storageName(vars.bucket)} takes objects up to ${String(vars.max)} bytes.`
|
|
21098
21154
|
},
|
|
21099
21155
|
MIME_TYPE_NOT_ALLOWED: {
|
|
21100
21156
|
status: 415,
|
|
@@ -25840,7 +25896,7 @@ function claimUpload(i) {
|
|
|
25840
25896
|
const nonce = nonceOf(i.nonce);
|
|
25841
25897
|
return compose(
|
|
25842
25898
|
[
|
|
25843
|
-
`INSERT INTO ${STORAGE_OBJECTS_TABLE} (id, bucket_id, name, owner, owner_id, metadata, version, parent, size, status, lock_id, lock_expires_at) SELECT ?, ?, ?, ?, ?, ?, NULL, ?, ?, 'uploading', ?, ? WHERE CASE WHEN NOT EXISTS (SELECT 1 FROM ${STORAGE_BUCKETS_TABLE} WHERE id = ? AND ${LIVE_BUCKET}) THEN ${marker("bucket", nonce)} WHEN (`,
|
|
25899
|
+
`INSERT INTO ${STORAGE_OBJECTS_TABLE} (id, bucket_id, name, owner, owner_id, metadata, version, parent, size, status, lock_id, lock_expires_at, pending) SELECT ?, ?, ?, ?, ?, ?, NULL, ?, ?, 'uploading', ?, ?, ? WHERE CASE WHEN NOT EXISTS (SELECT 1 FROM ${STORAGE_BUCKETS_TABLE} WHERE id = ? AND ${LIVE_BUCKET}) THEN ${marker("bucket", nonce)} WHEN (`,
|
|
25844
25900
|
i.check,
|
|
25845
25901
|
`) <> 1 THEN 0 WHEN EXISTS (SELECT 1 FROM ${STORAGE_OBJECTS_TABLE} WHERE bucket_id = ? AND name = ? AND status = 'ready') THEN ${marker("exists", nonce)} WHEN EXISTS (SELECT 1 FROM ${STORAGE_OBJECTS_TABLE} WHERE bucket_id = ? AND name = ?) THEN ${marker("busy", nonce)} WHEN ${PROJECT_BYTES} + ? > ? THEN ${marker("quota", nonce)} ELSE 1 END RETURNING id`
|
|
25846
25902
|
],
|
|
@@ -25856,6 +25912,7 @@ function claimUpload(i) {
|
|
|
25856
25912
|
i.size,
|
|
25857
25913
|
i.lockId,
|
|
25858
25914
|
i.expiresAt,
|
|
25915
|
+
i.pending ?? null,
|
|
25859
25916
|
i.bucket
|
|
25860
25917
|
],
|
|
25861
25918
|
[i.bucket, i.name, i.bucket, i.name, i.size, i.limitBytes]
|
|
@@ -25879,6 +25936,13 @@ function overwriteCheckRow(i) {
|
|
|
25879
25936
|
}
|
|
25880
25937
|
function claimOverwrite(i) {
|
|
25881
25938
|
const nonce = nonceOf(i.nonce);
|
|
25939
|
+
const room = i.inParts ? {
|
|
25940
|
+
sql: `${PROJECT_BYTES} + ? > ?`,
|
|
25941
|
+
values: [i.size, i.limitBytes]
|
|
25942
|
+
} : {
|
|
25943
|
+
sql: `? > o.size AND ${PROJECT_BYTES} + ? - o.size > ?`,
|
|
25944
|
+
values: [i.size, i.size, i.limitBytes]
|
|
25945
|
+
};
|
|
25882
25946
|
return compose(
|
|
25883
25947
|
[
|
|
25884
25948
|
`UPDATE ${STORAGE_OBJECTS_TABLE} AS o SET lock_id = ?, lock_expires_at = ?, pending = ? WHERE o.bucket_id = ? AND o.name = ? AND CASE WHEN NOT (`,
|
|
@@ -25886,17 +25950,14 @@ function claimOverwrite(i) {
|
|
|
25886
25950
|
// The row's own bucket (`o.bucket_id`), so no value is bound for it.
|
|
25887
25951
|
`) THEN ${marker("hidden", nonce)} WHEN NOT EXISTS (SELECT 1 FROM ${STORAGE_BUCKETS_TABLE} AS b WHERE b.id = o.bucket_id AND b.${LIVE_BUCKET}) THEN ${marker("bucket", nonce)} WHEN o.status <> 'ready' OR o.lock_id IS NOT NULL THEN ${marker("busy", nonce)} WHEN NOT (`,
|
|
25888
25952
|
i.updateUsing,
|
|
25889
|
-
`) THEN ${marker("denied", nonce)} WHEN
|
|
25953
|
+
`) THEN ${marker("denied", nonce)} WHEN ${room.sql} THEN ${marker("quota", nonce)} ELSE (`,
|
|
25890
25954
|
i.check,
|
|
25891
25955
|
// RETURNING names the columns bare: it does not see the UPDATE's
|
|
25892
25956
|
// alias (SQLite 3.53: `no such column: o.id`), and the table is the
|
|
25893
25957
|
// only one there.
|
|
25894
25958
|
") END RETURNING id, version, size, owner, owner_id, created_at"
|
|
25895
25959
|
],
|
|
25896
|
-
[
|
|
25897
|
-
[i.lockId, i.expiresAt, i.pending, i.bucket, i.name],
|
|
25898
|
-
[i.size, i.size, i.limitBytes]
|
|
25899
|
-
]
|
|
25960
|
+
[[i.lockId, i.expiresAt, i.pending, i.bucket, i.name], room.values]
|
|
25900
25961
|
);
|
|
25901
25962
|
}
|
|
25902
25963
|
function claimDelete(i) {
|
|
@@ -27075,6 +27136,17 @@ function formatRedirectEntry(entry) {
|
|
|
27075
27136
|
return entry.path === "/" ? entry.origin : `${entry.origin}${entry.path}`;
|
|
27076
27137
|
}
|
|
27077
27138
|
|
|
27139
|
+
// ../../packages/data-core/src/storage-multipart.ts
|
|
27140
|
+
var MiB = 1024 * 1024;
|
|
27141
|
+
var MULTIPART_PART_SIZES = [16 * MiB, 32 * MiB, 64 * MiB];
|
|
27142
|
+
var MAX_MULTIPART_PARTS = 1e4;
|
|
27143
|
+
var MAX_MULTIPART_BYTES = 64 * MiB * MAX_MULTIPART_PARTS;
|
|
27144
|
+
var MULTIPART_LOCK_MS = 60 * 60 * 1e3;
|
|
27145
|
+
var MULTIPART_PART_EXTEND_BELOW_MS = 30 * 60 * 1e3;
|
|
27146
|
+
var MULTIPART_COMPLETE_LOCK_MS = 15 * 60 * 1e3;
|
|
27147
|
+
var UUID = "[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}";
|
|
27148
|
+
var UPLOAD_ID = new RegExp(`^(${UUID})\\.(${UUID})$`);
|
|
27149
|
+
|
|
27078
27150
|
// ../../packages/data-core/src/system-sql.ts
|
|
27079
27151
|
async function convertLegacyPolicies(db, found) {
|
|
27080
27152
|
const rows = rowsOf(await db.prepare(POLICY_SQL.list).all());
|
|
@@ -28203,7 +28275,9 @@ function manifest(artifact) {
|
|
|
28203
28275
|
};
|
|
28204
28276
|
}
|
|
28205
28277
|
async function uploadArtifact(artifact, opts) {
|
|
28206
|
-
|
|
28278
|
+
const started = Date.now();
|
|
28279
|
+
const report = await uploadArtifactFiles(artifact, uploadPorts(opts));
|
|
28280
|
+
const uploadSummary = artifactUploadSummary(report, Date.now() - started);
|
|
28207
28281
|
const response = await authed(opts, "/artifacts/complete", {
|
|
28208
28282
|
method: "POST",
|
|
28209
28283
|
headers: { "Content-Type": "application/json" },
|
|
@@ -28214,7 +28288,8 @@ async function uploadArtifact(artifact, opts) {
|
|
|
28214
28288
|
throw new Error("Control plane did not return an artifact key.");
|
|
28215
28289
|
return {
|
|
28216
28290
|
artifactKey: payload.artifactKey,
|
|
28217
|
-
previewUrl: typeof payload.previewUrl === "string" ? payload.previewUrl : null
|
|
28291
|
+
previewUrl: typeof payload.previewUrl === "string" ? payload.previewUrl : null,
|
|
28292
|
+
uploadSummary
|
|
28218
28293
|
};
|
|
28219
28294
|
}
|
|
28220
28295
|
|
|
@@ -28290,7 +28365,8 @@ function eventsObserver(cp3, deploymentId, token, fetchImpl = fetch, logIngestUr
|
|
|
28290
28365
|
await flushLogs();
|
|
28291
28366
|
await post({ type: "metadata", metadata: rest }).catch(() => {
|
|
28292
28367
|
});
|
|
28293
|
-
}
|
|
28368
|
+
},
|
|
28369
|
+
flush: flushLogs
|
|
28294
28370
|
};
|
|
28295
28371
|
}
|
|
28296
28372
|
function slugify2(value) {
|
|
@@ -28485,12 +28561,19 @@ async function deployDirectory(sourceDir, options, log2) {
|
|
|
28485
28561
|
{ details: { deploymentId: ctx.deploymentId }, cause: error51 }
|
|
28486
28562
|
);
|
|
28487
28563
|
}
|
|
28488
|
-
await uploadArtifact(artifact, {
|
|
28564
|
+
const { uploadSummary } = await uploadArtifact(artifact, {
|
|
28489
28565
|
controlPlaneUrl: cpUrl,
|
|
28490
28566
|
deploymentId: ctx.deploymentId,
|
|
28491
28567
|
callbackToken: ctx.callbackToken,
|
|
28492
28568
|
...options.fetch ? { fetch: options.fetch } : {}
|
|
28493
28569
|
});
|
|
28570
|
+
log2(`\u25B8 ${uploadSummary}`);
|
|
28571
|
+
await observer.onLog({
|
|
28572
|
+
level: "INFO",
|
|
28573
|
+
stage: "DEPLOYING",
|
|
28574
|
+
message: uploadSummary
|
|
28575
|
+
});
|
|
28576
|
+
await observer.flush();
|
|
28494
28577
|
log2("\u25B8 publishing worker");
|
|
28495
28578
|
const detail = await waitForFinish(client, ctx, log2);
|
|
28496
28579
|
const url2 = deploymentUrl(detail, ctx, production);
|
|
@@ -28568,7 +28651,6 @@ export {
|
|
|
28568
28651
|
parseCacheControl,
|
|
28569
28652
|
STORAGE_OBJECTS_TABLE,
|
|
28570
28653
|
systemTablePredicate,
|
|
28571
|
-
dataError,
|
|
28572
28654
|
SCHEMA_SQL,
|
|
28573
28655
|
tableNames,
|
|
28574
28656
|
snapshotFromRows,
|
package/dist/deploy.d.ts
CHANGED
|
@@ -21,7 +21,10 @@ export interface DeployBuildExecutor {
|
|
|
21
21
|
export declare function parseDotenv(text: string): Record<string, string>;
|
|
22
22
|
/** Reads the first of .env.local / .env.production / .env in a directory. */
|
|
23
23
|
export declare function readProjectEnv(dir: string): Record<string, string>;
|
|
24
|
-
export declare function eventsObserver(cp: string, deploymentId: string, token: string, fetchImpl?: typeof fetch, logIngestUrl?: string): BuildObserver
|
|
24
|
+
export declare function eventsObserver(cp: string, deploymentId: string, token: string, fetchImpl?: typeof fetch, logIngestUrl?: string): BuildObserver & {
|
|
25
|
+
/** Sends what is buffered now; never throws. */
|
|
26
|
+
flush(): Promise<void>;
|
|
27
|
+
};
|
|
25
28
|
export declare function slugify(value: string): string;
|
|
26
29
|
export interface DeployOptions {
|
|
27
30
|
name?: string | undefined;
|
package/dist/deploy.js
CHANGED
package/dist/index.js
CHANGED
|
@@ -26,7 +26,6 @@ import {
|
|
|
26
26
|
controlPlaneUrl,
|
|
27
27
|
countLintedTables,
|
|
28
28
|
createAuthClient,
|
|
29
|
-
dataError,
|
|
30
29
|
deployDirectory,
|
|
31
30
|
detectGitInfo,
|
|
32
31
|
ensureGitignoreHasNaro,
|
|
@@ -83,7 +82,7 @@ import {
|
|
|
83
82
|
toNaroError,
|
|
84
83
|
unreadablePolicyComment,
|
|
85
84
|
writeProjectLink
|
|
86
|
-
} from "./chunk-
|
|
85
|
+
} from "./chunk-XNT7SINN.js";
|
|
87
86
|
import "./chunk-K3NQKI34.js";
|
|
88
87
|
|
|
89
88
|
// src/args.ts
|
|
@@ -289,6 +288,13 @@ var Output = class {
|
|
|
289
288
|
this.streams.stderr(`${message}
|
|
290
289
|
`);
|
|
291
290
|
}
|
|
291
|
+
/**
|
|
292
|
+
* Text on stderr exactly as given, with no line break added: a progress
|
|
293
|
+
* line a terminal rewrites in place (`\r…`). Never stdout, in either mode.
|
|
294
|
+
*/
|
|
295
|
+
progress(text) {
|
|
296
|
+
this.streams.stderr(text);
|
|
297
|
+
}
|
|
292
298
|
/** Extra diagnostics, only with --debug. */
|
|
293
299
|
debugLog(message) {
|
|
294
300
|
if (this.debug) this.streams.stderr(`[debug] ${message}
|
|
@@ -5337,7 +5343,7 @@ function meterLines(info) {
|
|
|
5337
5343
|
}
|
|
5338
5344
|
if (today.bytesBlocked)
|
|
5339
5345
|
lines.push(
|
|
5340
|
-
today.measuredBytes === null ? `\u2717 Uploads are stopped: the bucket is at the storage limit of ${binarySize(limits.bytes)}. Delete objects; uploads reopen after the next measurement (usually within an hour).` : `\u2717 Uploads are stopped: R2 holds ${binarySize(today.measuredBytes, 2)} of ${binarySize(limits.bytes)}. Delete objects; uploads reopen after the next measurement (usually within an hour).`
|
|
5346
|
+
today.measuredBytes === null ? `\u2717 Uploads are stopped: the bucket is at the storage limit of ${binarySize(limits.bytes)}. Delete objects; uploads reopen after the next measurement (usually within an hour and a half).` : `\u2717 Uploads are stopped: R2 holds ${binarySize(today.measuredBytes, 2)} of ${binarySize(limits.bytes)}. Delete objects; uploads reopen after the next measurement (usually within an hour and a half).`
|
|
5341
5347
|
);
|
|
5342
5348
|
if (today.opsBlocked)
|
|
5343
5349
|
lines.push(
|
|
@@ -5362,7 +5368,7 @@ function storageLines(info) {
|
|
|
5362
5368
|
const { limits, usage } = info;
|
|
5363
5369
|
return [
|
|
5364
5370
|
`Storage: on (R2 bucket ${printable(info.bucket ?? "unknown")})`,
|
|
5365
|
-
`Limits: ${binarySize(limits.bytes)} in all, ${binarySize(limits.objectBytes)} per object, ${binarySize(limits.singleUploadBytes)} per
|
|
5371
|
+
`Limits: ${binarySize(limits.bytes)} in all, ${binarySize(limits.objectBytes)} per object, ${binarySize(limits.singleUploadBytes)} per request (larger files go in parts)`,
|
|
5366
5372
|
usage === null ? "Used: not known yet: the backend is not ready (`naro backend` shows where it got to)" : `Used: ${binarySize(usage.bytes)} in ${count(usage.objects, "object")}`,
|
|
5367
5373
|
...meterLines(info),
|
|
5368
5374
|
info.buckets.length === 0 ? "Buckets: none yet (`naro storage bucket create <name>` makes one)" : `Buckets: ${String(info.buckets.length)}`,
|
|
@@ -5549,6 +5555,12 @@ function requestTimeout(ms) {
|
|
|
5549
5555
|
message: `The Naro backend did not answer within ${String(Math.round(ms / 1e3))} seconds. Check the network and try again.`
|
|
5550
5556
|
};
|
|
5551
5557
|
}
|
|
5558
|
+
function uploadAborted() {
|
|
5559
|
+
return {
|
|
5560
|
+
code: "ABORTED",
|
|
5561
|
+
message: "The upload was stopped before it finished."
|
|
5562
|
+
};
|
|
5563
|
+
}
|
|
5552
5564
|
function invalidResponse(status) {
|
|
5553
5565
|
return {
|
|
5554
5566
|
code: "INVALID_RESPONSE",
|
|
@@ -5714,6 +5726,12 @@ async function send(config, options) {
|
|
|
5714
5726
|
credentials: "omit"
|
|
5715
5727
|
};
|
|
5716
5728
|
if (options.body !== void 0) init.body = options.body;
|
|
5729
|
+
const { signal } = options;
|
|
5730
|
+
if (signal?.aborted) return { error: uploadAborted(), status: null };
|
|
5731
|
+
const controller = signal ? new AbortController() : null;
|
|
5732
|
+
const stop = () => controller?.abort();
|
|
5733
|
+
if (controller) init.signal = controller.signal;
|
|
5734
|
+
signal?.addEventListener("abort", stop);
|
|
5717
5735
|
let response;
|
|
5718
5736
|
try {
|
|
5719
5737
|
const fetchImpl = config.fetch ?? globalThis.fetch;
|
|
@@ -5722,26 +5740,34 @@ async function send(config, options) {
|
|
|
5722
5740
|
init
|
|
5723
5741
|
);
|
|
5724
5742
|
} catch (error2) {
|
|
5725
|
-
return {
|
|
5743
|
+
return {
|
|
5744
|
+
error: signal?.aborted ? uploadAborted() : networkError(error2),
|
|
5745
|
+
status: null
|
|
5746
|
+
};
|
|
5747
|
+
} finally {
|
|
5748
|
+
signal?.removeEventListener("abort", stop);
|
|
5726
5749
|
}
|
|
5727
|
-
|
|
5728
|
-
|
|
5750
|
+
const { status } = response;
|
|
5751
|
+
if (status >= 200 && status < 300) return { error: null, response };
|
|
5729
5752
|
let text;
|
|
5730
5753
|
try {
|
|
5731
5754
|
text = await response.text();
|
|
5732
5755
|
} catch (error2) {
|
|
5733
|
-
return { error: networkError(error2) };
|
|
5756
|
+
return { error: networkError(error2), status: null };
|
|
5734
5757
|
}
|
|
5735
5758
|
let body;
|
|
5736
5759
|
if (text.trim() !== "") {
|
|
5737
5760
|
try {
|
|
5738
5761
|
body = JSON.parse(text);
|
|
5739
5762
|
} catch {
|
|
5740
|
-
return { error: invalidResponse(
|
|
5763
|
+
return { error: invalidResponse(status), status };
|
|
5741
5764
|
}
|
|
5742
5765
|
}
|
|
5743
|
-
const error = errorFromBody(
|
|
5744
|
-
return {
|
|
5766
|
+
const error = errorFromBody(status, body) ?? invalidResponse(status);
|
|
5767
|
+
return {
|
|
5768
|
+
error: await settled(config, error, response.headers, token),
|
|
5769
|
+
status
|
|
5770
|
+
};
|
|
5745
5771
|
}
|
|
5746
5772
|
var RETRY_AFTER_CODES = /* @__PURE__ */ new Set([
|
|
5747
5773
|
"RATE_LIMITED",
|
|
@@ -7312,10 +7338,176 @@ var TableBuilder = class {
|
|
|
7312
7338
|
});
|
|
7313
7339
|
}
|
|
7314
7340
|
};
|
|
7341
|
+
function fail(error) {
|
|
7342
|
+
return { data: null, error };
|
|
7343
|
+
}
|
|
7344
|
+
var isUploadResult = (value) => isRecord(value) && typeof value.id === "string" && typeof value.path === "string" && typeof value.fullPath === "string";
|
|
7345
|
+
function dataOf2(status, body, guard) {
|
|
7346
|
+
if (isRecord(body) && Object.hasOwn(body, "data") && guard(body.data))
|
|
7347
|
+
return { data: body.data, error: null };
|
|
7348
|
+
return fail(invalidResponse(status));
|
|
7349
|
+
}
|
|
7350
|
+
async function readJson(response, guard) {
|
|
7351
|
+
let text;
|
|
7352
|
+
try {
|
|
7353
|
+
text = await response.text();
|
|
7354
|
+
} catch (error) {
|
|
7355
|
+
return fail(networkError(error));
|
|
7356
|
+
}
|
|
7357
|
+
let body;
|
|
7358
|
+
try {
|
|
7359
|
+
body = JSON.parse(text);
|
|
7360
|
+
} catch {
|
|
7361
|
+
return fail(invalidResponse(response.status));
|
|
7362
|
+
}
|
|
7363
|
+
return dataOf2(response.status, body, guard);
|
|
7364
|
+
}
|
|
7365
|
+
var ROUTE = "/storage/upload/multipart";
|
|
7366
|
+
var CONCURRENCY = 4;
|
|
7367
|
+
var ATTEMPTS = 4;
|
|
7368
|
+
var BACKOFF_MS = [1e3, 2e3, 4e3];
|
|
7369
|
+
var UPLOAD_ID = /^[\w-][\w.-]*$/;
|
|
7370
|
+
var positive = (value) => typeof value === "number" && Number.isSafeInteger(value) && value > 0;
|
|
7371
|
+
var planFor = (size) => (value) => isRecord(value) && typeof value.uploadId === "string" && UPLOAD_ID.test(value.uploadId) && positive(value.partSize) && positive(value.partCount) && (value.partCount - 1) * value.partSize < size && value.partCount * value.partSize >= size;
|
|
7372
|
+
var partAnswer = (n) => (value) => isRecord(value) && value.partNumber === n && typeof value.etag === "string" && value.etag !== "";
|
|
7373
|
+
var retryable = (error, status) => error.code === "NETWORK_ERROR" || error.code === "OBJECT_RATE_LIMITED" || status !== null && status >= 500 && error.code !== "STORAGE_QUOTA_EXCEEDED";
|
|
7374
|
+
function waitAfter(error, attempt22) {
|
|
7375
|
+
const after = isRecord(error.details) ? error.details.retryAfter : void 0;
|
|
7376
|
+
if (typeof after === "number") return after * 1e3;
|
|
7377
|
+
if (error.code === "OBJECT_RATE_LIMITED") return 1e3;
|
|
7378
|
+
return BACKOFF_MS[attempt22 - 1] ?? 4e3;
|
|
7379
|
+
}
|
|
7380
|
+
var sleepFor = (ms, signal) => new Promise((done) => {
|
|
7381
|
+
if (signal?.aborted) {
|
|
7382
|
+
done();
|
|
7383
|
+
return;
|
|
7384
|
+
}
|
|
7385
|
+
const end = () => {
|
|
7386
|
+
clearTimeout(timer);
|
|
7387
|
+
signal?.removeEventListener("abort", end);
|
|
7388
|
+
done();
|
|
7389
|
+
};
|
|
7390
|
+
const timer = setTimeout(end, ms);
|
|
7391
|
+
signal?.addEventListener("abort", end);
|
|
7392
|
+
});
|
|
7393
|
+
async function uploadInParts(a) {
|
|
7394
|
+
const { signal } = a;
|
|
7395
|
+
const sleep2 = a.sleep ?? sleepFor;
|
|
7396
|
+
if (signal?.aborted) return fail(uploadAborted());
|
|
7397
|
+
const started = await call(a.http, {
|
|
7398
|
+
method: a.method,
|
|
7399
|
+
path: ROUTE,
|
|
7400
|
+
body: {
|
|
7401
|
+
bucket: a.bucket,
|
|
7402
|
+
path: a.path,
|
|
7403
|
+
size: a.size,
|
|
7404
|
+
contentType: a.contentType,
|
|
7405
|
+
cacheControl: a.cacheControl,
|
|
7406
|
+
// PUT always replaces: the worker refuses `upsert` on it.
|
|
7407
|
+
...a.method === "POST" && a.upsert ? { upsert: true } : {}
|
|
7408
|
+
},
|
|
7409
|
+
withToken: true
|
|
7410
|
+
});
|
|
7411
|
+
if (started.error !== null) return fail(started.error);
|
|
7412
|
+
const plan = dataOf2(
|
|
7413
|
+
started.answer.status,
|
|
7414
|
+
started.answer.body,
|
|
7415
|
+
planFor(a.size)
|
|
7416
|
+
);
|
|
7417
|
+
if (plan.error !== null) return fail(plan.error);
|
|
7418
|
+
const { uploadId, partSize, partCount } = plan.data;
|
|
7419
|
+
const base = `${ROUTE}/${uploadId}`;
|
|
7420
|
+
const progress = (sentBytes2) => {
|
|
7421
|
+
if (signal?.aborted) return;
|
|
7422
|
+
try {
|
|
7423
|
+
const result = a.onProgress?.(sentBytes2, a.size);
|
|
7424
|
+
if (isThenable(result)) result.then(void 0, () => void 0);
|
|
7425
|
+
} catch {
|
|
7426
|
+
}
|
|
7427
|
+
};
|
|
7428
|
+
const callOff = async () => {
|
|
7429
|
+
await call(a.http, { method: "DELETE", path: base, withToken: true }).catch(
|
|
7430
|
+
() => void 0
|
|
7431
|
+
);
|
|
7432
|
+
};
|
|
7433
|
+
const etags = [];
|
|
7434
|
+
const failure = {
|
|
7435
|
+
error: signal?.aborted ? uploadAborted() : null
|
|
7436
|
+
};
|
|
7437
|
+
let next = 1;
|
|
7438
|
+
let sentBytes = 0;
|
|
7439
|
+
const onAbort = () => {
|
|
7440
|
+
failure.error ??= uploadAborted();
|
|
7441
|
+
};
|
|
7442
|
+
const tryPart = async (n, bytes) => {
|
|
7443
|
+
const sent = await send(a.http, {
|
|
7444
|
+
method: "PUT",
|
|
7445
|
+
path: `${base}/${String(n)}`,
|
|
7446
|
+
headers: { "content-type": "application/octet-stream" },
|
|
7447
|
+
body: bytes,
|
|
7448
|
+
withToken: true,
|
|
7449
|
+
signal
|
|
7450
|
+
});
|
|
7451
|
+
if (sent.error !== null) return sent;
|
|
7452
|
+
const read = await readJson(sent.response, partAnswer(n));
|
|
7453
|
+
if (read.error !== null)
|
|
7454
|
+
return { error: read.error, status: sent.response.status };
|
|
7455
|
+
return { etag: read.data.etag };
|
|
7456
|
+
};
|
|
7457
|
+
const sendPart = async (n) => {
|
|
7458
|
+
const from = (n - 1) * partSize;
|
|
7459
|
+
const to = Math.min(from + partSize, a.size);
|
|
7460
|
+
const bytes = a.body instanceof Uint8Array ? a.body.subarray(from, to) : a.body.slice(from, to);
|
|
7461
|
+
for (let attempt22 = 1; ; attempt22 += 1) {
|
|
7462
|
+
const tried = await tryPart(n, bytes);
|
|
7463
|
+
if ("etag" in tried) {
|
|
7464
|
+
etags[n - 1] = tried.etag;
|
|
7465
|
+
sentBytes += to - from;
|
|
7466
|
+
progress(sentBytes);
|
|
7467
|
+
return null;
|
|
7468
|
+
}
|
|
7469
|
+
if (attempt22 === ATTEMPTS || failure.error !== null || !retryable(tried.error, tried.status))
|
|
7470
|
+
return tried.error;
|
|
7471
|
+
await sleep2(waitAfter(tried.error, attempt22), signal);
|
|
7472
|
+
if (failure.error !== null) return tried.error;
|
|
7473
|
+
}
|
|
7474
|
+
};
|
|
7475
|
+
const worker = async () => {
|
|
7476
|
+
while (failure.error === null && next <= partCount) {
|
|
7477
|
+
const n = next;
|
|
7478
|
+
next += 1;
|
|
7479
|
+
const error = await sendPart(n);
|
|
7480
|
+
if (error !== null) failure.error ??= error;
|
|
7481
|
+
}
|
|
7482
|
+
};
|
|
7483
|
+
signal?.addEventListener("abort", onAbort);
|
|
7484
|
+
await Promise.all(
|
|
7485
|
+
Array.from({ length: Math.min(CONCURRENCY, partCount) }, worker)
|
|
7486
|
+
);
|
|
7487
|
+
signal?.removeEventListener("abort", onAbort);
|
|
7488
|
+
if (failure.error !== null) {
|
|
7489
|
+
await callOff();
|
|
7490
|
+
return fail(failure.error);
|
|
7491
|
+
}
|
|
7492
|
+
const completed = await call(a.http, {
|
|
7493
|
+
method: "POST",
|
|
7494
|
+
path: `${base}/complete`,
|
|
7495
|
+
body: {
|
|
7496
|
+
parts: etags.map((etag, i) => ({ partNumber: i + 1, etag }))
|
|
7497
|
+
},
|
|
7498
|
+
withToken: true
|
|
7499
|
+
});
|
|
7500
|
+
if (completed.error !== null) {
|
|
7501
|
+
if (completed.answer !== null && completed.error.code !== "INVALID_RESPONSE")
|
|
7502
|
+
await callOff();
|
|
7503
|
+
return fail(completed.error);
|
|
7504
|
+
}
|
|
7505
|
+
return dataOf2(completed.answer.status, completed.answer.body, isUploadResult);
|
|
7506
|
+
}
|
|
7507
|
+
var isThenable = (value) => typeof value === "object" && value !== null && typeof value.then === "function";
|
|
7315
7508
|
var MAX_SINGLE_UPLOAD_BYTES2 = 52428800;
|
|
7316
7509
|
var STORAGE_MESSAGES = {
|
|
7317
7510
|
bucket: "The bucket name is not valid.",
|
|
7318
|
-
tooLarge: "Uploads over 50 MiB are not supported yet.",
|
|
7319
7511
|
request: "The storage request is not valid.",
|
|
7320
7512
|
body: "upload takes a Blob, File, ArrayBuffer, typed array or string."
|
|
7321
7513
|
};
|
|
@@ -7340,9 +7532,6 @@ function invalidPath(reason) {
|
|
|
7340
7532
|
message: `The object path is not valid: it ${reason}.`
|
|
7341
7533
|
};
|
|
7342
7534
|
}
|
|
7343
|
-
function objectTooLarge() {
|
|
7344
|
-
return { code: "OBJECT_TOO_LARGE", message: STORAGE_MESSAGES.tooLarge };
|
|
7345
|
-
}
|
|
7346
7535
|
function encodePath(path) {
|
|
7347
7536
|
if (typeof path !== "string" || path === "")
|
|
7348
7537
|
return { ok: false, error: invalidPath("is empty") };
|
|
@@ -7379,6 +7568,16 @@ function encodeBucket(bucket) {
|
|
|
7379
7568
|
};
|
|
7380
7569
|
return { ok: true, encoded: encodeURIComponent(bucket) };
|
|
7381
7570
|
}
|
|
7571
|
+
function objectRoute(bucket, path) {
|
|
7572
|
+
const encodedBucket = encodeBucket(bucket);
|
|
7573
|
+
if (!encodedBucket.ok) return encodedBucket;
|
|
7574
|
+
const encodedPath = encodePath(path);
|
|
7575
|
+
if (!encodedPath.ok) return encodedPath;
|
|
7576
|
+
return {
|
|
7577
|
+
ok: true,
|
|
7578
|
+
encoded: `${encodedBucket.encoded}/${encodedPath.encoded}`
|
|
7579
|
+
};
|
|
7580
|
+
}
|
|
7382
7581
|
var isArrayBuffer = (value) => Object.prototype.toString.call(value) === "[object ArrayBuffer]";
|
|
7383
7582
|
function isDetached(buffer) {
|
|
7384
7583
|
if (!isArrayBuffer(buffer)) return false;
|
|
@@ -7459,36 +7658,12 @@ function payloadOf(body, contentType) {
|
|
|
7459
7658
|
};
|
|
7460
7659
|
}
|
|
7461
7660
|
var SIGNED_PREFIX = "/_naro/v1/storage/object/sign/";
|
|
7462
|
-
function fail(error) {
|
|
7463
|
-
return { data: null, error };
|
|
7464
|
-
}
|
|
7465
|
-
var isUploadResult = (value) => isRecord(value) && typeof value.id === "string" && typeof value.path === "string" && typeof value.fullPath === "string";
|
|
7466
7661
|
var isFileObject = (value) => isRecord(value) && typeof value.id === "string" && typeof value.name === "string" && isRecord(value.metadata);
|
|
7467
7662
|
var isEntry2 = (value) => isRecord(value) && typeof value.name === "string" && (value.id === null || isFileObject(value));
|
|
7468
7663
|
var isSigned = (value) => isRecord(value) && typeof value.signedUrl === "string" && value.signedUrl.startsWith(SIGNED_PREFIX);
|
|
7469
7664
|
function listOf(guard) {
|
|
7470
7665
|
return (value) => Array.isArray(value) && value.every(guard);
|
|
7471
7666
|
}
|
|
7472
|
-
function dataOf2(status, body, guard) {
|
|
7473
|
-
if (isRecord(body) && Object.hasOwn(body, "data") && guard(body.data))
|
|
7474
|
-
return { data: body.data, error: null };
|
|
7475
|
-
return fail(invalidResponse(status));
|
|
7476
|
-
}
|
|
7477
|
-
async function readJson(response, guard) {
|
|
7478
|
-
let text;
|
|
7479
|
-
try {
|
|
7480
|
-
text = await response.text();
|
|
7481
|
-
} catch (error) {
|
|
7482
|
-
return fail(networkError(error));
|
|
7483
|
-
}
|
|
7484
|
-
let body;
|
|
7485
|
-
try {
|
|
7486
|
-
body = JSON.parse(text);
|
|
7487
|
-
} catch {
|
|
7488
|
-
return fail(invalidResponse(response.status));
|
|
7489
|
-
}
|
|
7490
|
-
return dataOf2(response.status, body, guard);
|
|
7491
|
-
}
|
|
7492
7667
|
var StorageFileApi = class {
|
|
7493
7668
|
#http;
|
|
7494
7669
|
#auth;
|
|
@@ -7500,14 +7675,9 @@ var StorageFileApi = class {
|
|
|
7500
7675
|
}
|
|
7501
7676
|
/** `/storage/object/<route><bucket>/<path>`, or the refusal no request needs. */
|
|
7502
7677
|
#objectPath(route2, path) {
|
|
7503
|
-
const
|
|
7504
|
-
if (!
|
|
7505
|
-
|
|
7506
|
-
if (!name.ok) return name;
|
|
7507
|
-
return {
|
|
7508
|
-
ok: true,
|
|
7509
|
-
path: `/storage/object/${route2}${bucket.encoded}/${name.encoded}`
|
|
7510
|
-
};
|
|
7678
|
+
const target = objectRoute(this.#bucket, path);
|
|
7679
|
+
if (!target.ok) return target;
|
|
7680
|
+
return { ok: true, path: `/storage/object/${route2}${target.encoded}` };
|
|
7511
7681
|
}
|
|
7512
7682
|
/** `/storage/object/<route><bucket>`, for the routes that take names in the body. */
|
|
7513
7683
|
#bucketPath(route2) {
|
|
@@ -7529,8 +7699,24 @@ var StorageFileApi = class {
|
|
|
7529
7699
|
const cache = `max-age=${options.cacheControl ?? "3600"}`;
|
|
7530
7700
|
const unreadable = headerProblem(options.contentType, cache);
|
|
7531
7701
|
if (unreadable !== null) return fail(unreadable);
|
|
7532
|
-
|
|
7702
|
+
const { signal } = options;
|
|
7703
|
+
if (signal?.aborted) return fail(uploadAborted());
|
|
7533
7704
|
await this.#auth.ready();
|
|
7705
|
+
if (payload.size > MAX_SINGLE_UPLOAD_BYTES2)
|
|
7706
|
+
return uploadInParts({
|
|
7707
|
+
http: this.#http,
|
|
7708
|
+
method,
|
|
7709
|
+
bucket: this.#bucket,
|
|
7710
|
+
path,
|
|
7711
|
+
// No copy: a Blob stays lazy, a Uint8Array is viewed, an ArrayBuffer wrapped.
|
|
7712
|
+
body: isArrayBuffer(payload.body) ? new Uint8Array(payload.body) : payload.body,
|
|
7713
|
+
size: payload.size,
|
|
7714
|
+
contentType: payload.type,
|
|
7715
|
+
cacheControl: cache,
|
|
7716
|
+
upsert,
|
|
7717
|
+
...options.onProgress ? { onProgress: options.onProgress } : {},
|
|
7718
|
+
signal
|
|
7719
|
+
});
|
|
7534
7720
|
const headers = {
|
|
7535
7721
|
"content-type": payload.type,
|
|
7536
7722
|
"cache-control": cache
|
|
@@ -7541,7 +7727,9 @@ var StorageFileApi = class {
|
|
|
7541
7727
|
path: target.path,
|
|
7542
7728
|
headers,
|
|
7543
7729
|
body: payload.body,
|
|
7544
|
-
withToken: true
|
|
7730
|
+
withToken: true,
|
|
7731
|
+
// The fetch stops with the body: the worker undoes its claim at once.
|
|
7732
|
+
signal
|
|
7545
7733
|
});
|
|
7546
7734
|
if (sent.error !== null) return fail(sent.error);
|
|
7547
7735
|
return readJson(sent.response, isUploadResult);
|
|
@@ -7787,6 +7975,7 @@ var CP_USAGE = "Usage: naro storage cp <from> <to> (one side naro://<bucket>/<pa
|
|
|
7787
7975
|
var RM_USAGE = "Usage: naro storage rm naro://<bucket>/<path>\u2026 [--recursive] [--yes]";
|
|
7788
7976
|
var LS_USAGE = "Usage: naro storage ls [naro://<bucket>[/<prefix>]] [--recursive]";
|
|
7789
7977
|
var BACKEND_OFF_MESSAGE = "The backend is turned off for this project. Turn it on with `naro backend enable` first.";
|
|
7978
|
+
var FOUR_GIB = 4294967296;
|
|
7790
7979
|
var PAGE = 1e3;
|
|
7791
7980
|
var MAX_OFFSET = 1e5;
|
|
7792
7981
|
var TRUNCATED_WARNING = `\u26A0 A folder's listing stopped at ${(MAX_OFFSET + PAGE).toLocaleString("en-US")} entries, as far as the API's offset reaches; there may be more.`;
|
|
@@ -7889,7 +8078,8 @@ async function objects(context) {
|
|
|
7889
8078
|
{ cause: error }
|
|
7890
8079
|
);
|
|
7891
8080
|
}
|
|
7892
|
-
|
|
8081
|
+
const url = new URL(backend.url.trim().replace(/\/+$/, "")).origin;
|
|
8082
|
+
return { projectId, storage: naro.storage, url, serviceKey };
|
|
7893
8083
|
}
|
|
7894
8084
|
function isFolder(entry) {
|
|
7895
8085
|
return entry.id === null;
|
|
@@ -7982,33 +8172,56 @@ async function storageLs(context) {
|
|
|
7982
8172
|
);
|
|
7983
8173
|
}
|
|
7984
8174
|
var INTERRUPTS = ["SIGINT", "SIGTERM", "SIGHUP"];
|
|
7985
|
-
function
|
|
7986
|
-
|
|
8175
|
+
function endWith(signal) {
|
|
8176
|
+
try {
|
|
8177
|
+
process.kill(process.pid, signal);
|
|
8178
|
+
} catch {
|
|
8179
|
+
process.exit(1);
|
|
8180
|
+
}
|
|
8181
|
+
}
|
|
8182
|
+
function onInterrupt(onSignal) {
|
|
8183
|
+
for (const signal of INTERRUPTS) process.on(signal, onSignal);
|
|
8184
|
+
return () => {
|
|
7987
8185
|
for (const signal of INTERRUPTS) process.off(signal, onSignal);
|
|
7988
8186
|
};
|
|
7989
|
-
|
|
8187
|
+
}
|
|
8188
|
+
function removeOnInterrupt(partial) {
|
|
8189
|
+
const stop = onInterrupt((signal) => {
|
|
7990
8190
|
stop();
|
|
7991
8191
|
try {
|
|
7992
8192
|
rmSync2(partial, { force: true });
|
|
7993
8193
|
} catch {
|
|
7994
8194
|
}
|
|
7995
|
-
|
|
7996
|
-
|
|
7997
|
-
} catch {
|
|
7998
|
-
process.exit(1);
|
|
7999
|
-
}
|
|
8000
|
-
}
|
|
8001
|
-
for (const signal of INTERRUPTS) process.on(signal, onSignal);
|
|
8195
|
+
endWith(signal);
|
|
8196
|
+
});
|
|
8002
8197
|
return stop;
|
|
8003
8198
|
}
|
|
8004
|
-
|
|
8199
|
+
function stopOnInterrupt(onFirst) {
|
|
8200
|
+
const controller = new AbortController();
|
|
8201
|
+
let caught = null;
|
|
8202
|
+
const stop = onInterrupt((signal) => {
|
|
8203
|
+
if (caught !== null) {
|
|
8204
|
+
stop();
|
|
8205
|
+
endWith(signal);
|
|
8206
|
+
return;
|
|
8207
|
+
}
|
|
8208
|
+
caught = signal;
|
|
8209
|
+
onFirst();
|
|
8210
|
+
controller.abort();
|
|
8211
|
+
});
|
|
8212
|
+
return {
|
|
8213
|
+
signal: controller.signal,
|
|
8214
|
+
settle: () => {
|
|
8215
|
+
stop();
|
|
8216
|
+
return caught;
|
|
8217
|
+
}
|
|
8218
|
+
};
|
|
8219
|
+
}
|
|
8220
|
+
async function writeAtomically(bytes, destination) {
|
|
8005
8221
|
const partial = join6(dirname3(destination), `.${randomUUID()}.naro-partial`);
|
|
8006
8222
|
const stopListening = removeOnInterrupt(partial);
|
|
8007
8223
|
try {
|
|
8008
|
-
await pipeline2(
|
|
8009
|
-
Readable.fromWeb(blob.stream()),
|
|
8010
|
-
createWriteStream2(partial, { flags: "wx" })
|
|
8011
|
-
);
|
|
8224
|
+
await pipeline2(bytes, createWriteStream2(partial, { flags: "wx" }));
|
|
8012
8225
|
await rename(partial, destination);
|
|
8013
8226
|
} catch (error) {
|
|
8014
8227
|
await rm(partial, { force: true }).catch(() => void 0);
|
|
@@ -8017,6 +8230,34 @@ async function writeAtomically(blob, destination) {
|
|
|
8017
8230
|
stopListening();
|
|
8018
8231
|
}
|
|
8019
8232
|
}
|
|
8233
|
+
function progressReporter(output, total) {
|
|
8234
|
+
const tty = process.stderr.isTTY === true;
|
|
8235
|
+
let lastTenth = -1;
|
|
8236
|
+
let lastLength = 0;
|
|
8237
|
+
let open = false;
|
|
8238
|
+
return {
|
|
8239
|
+
report(sent) {
|
|
8240
|
+
const percent2 = Math.floor(sent / total * 100);
|
|
8241
|
+
const line = `\u2191 ${binarySize(sent)} / ${binarySize(total)} (${String(percent2)}%)`;
|
|
8242
|
+
if (tty) {
|
|
8243
|
+
open = sent !== total;
|
|
8244
|
+
output.progress(`\r${line.padEnd(lastLength)}${open ? "" : "\n"}`);
|
|
8245
|
+
lastLength = line.length;
|
|
8246
|
+
return;
|
|
8247
|
+
}
|
|
8248
|
+
const tenth = Math.floor(percent2 / 10);
|
|
8249
|
+
if (tenth === lastTenth && sent !== total) return;
|
|
8250
|
+
lastTenth = tenth;
|
|
8251
|
+
output.progress(`${line}
|
|
8252
|
+
`);
|
|
8253
|
+
},
|
|
8254
|
+
close() {
|
|
8255
|
+
if (!open) return;
|
|
8256
|
+
open = false;
|
|
8257
|
+
output.progress("\n");
|
|
8258
|
+
}
|
|
8259
|
+
};
|
|
8260
|
+
}
|
|
8020
8261
|
async function upload(context, local, target) {
|
|
8021
8262
|
const file = resolve5(context.cwd, local);
|
|
8022
8263
|
const info = await stat(file).catch(() => null);
|
|
@@ -8035,32 +8276,49 @@ async function upload(context, local, target) {
|
|
|
8035
8276
|
"VALIDATION_FAILED",
|
|
8036
8277
|
`${printable(local)} is not a regular file.`
|
|
8037
8278
|
);
|
|
8038
|
-
if (info.size > MAX_SINGLE_UPLOAD_BYTES)
|
|
8039
|
-
throw new NaroError(
|
|
8040
|
-
"OBJECT_TOO_LARGE",
|
|
8041
|
-
dataError("OBJECT_TOO_LARGE", { single: true }).message
|
|
8042
|
-
);
|
|
8043
8279
|
const { cacheControl } = context.flags;
|
|
8044
8280
|
if (cacheControl !== void 0 && !isMaxAge(cacheControl))
|
|
8045
8281
|
throw new NaroError("VALIDATION_FAILED", CACHE_CONTROL_RULE);
|
|
8046
8282
|
const path = target.path === "" || target.path.endsWith("/") ? `${target.path}${basename2(file)}` : target.path;
|
|
8047
|
-
const { projectId, storage } = await objects(context);
|
|
8048
8283
|
const body = await openAsBlob(file);
|
|
8284
|
+
if (body.size !== info.size)
|
|
8285
|
+
throw new NaroError(
|
|
8286
|
+
"VALIDATION_FAILED",
|
|
8287
|
+
info.size >= FOUR_GIB ? `${printable(local)} is ${binarySize(info.size)}, but Node reads it as ${binarySize(body.size)}: naro storage cp cannot upload a file of 4 GiB or more yet.` : `${printable(local)} changed while it was being opened (${binarySize(info.size)}, then ${binarySize(body.size)}). Run the copy again once nothing is writing to it.`
|
|
8288
|
+
);
|
|
8289
|
+
const { projectId, storage } = await objects(context);
|
|
8049
8290
|
const contentType = context.flags.contentType ?? typeFor(file);
|
|
8050
|
-
const
|
|
8051
|
-
|
|
8291
|
+
const progress = body.size > MAX_SINGLE_UPLOAD_BYTES && !context.output.json ? progressReporter(context.output, body.size) : void 0;
|
|
8292
|
+
const interrupt = stopOnInterrupt(() => {
|
|
8293
|
+
progress?.close();
|
|
8294
|
+
context.output.progress("Stopping the upload\u2026\n");
|
|
8295
|
+
});
|
|
8296
|
+
let answer;
|
|
8297
|
+
let caught;
|
|
8298
|
+
try {
|
|
8299
|
+
answer = await storage.from(target.bucket).upload(path, body, {
|
|
8052
8300
|
...contentType === void 0 ? {} : { contentType },
|
|
8053
8301
|
...cacheControl === void 0 ? {} : { cacheControl },
|
|
8054
|
-
upsert: context.flags.upsert === true
|
|
8055
|
-
|
|
8056
|
-
|
|
8057
|
-
|
|
8058
|
-
|
|
8059
|
-
|
|
8060
|
-
|
|
8061
|
-
|
|
8062
|
-
|
|
8063
|
-
|
|
8302
|
+
upsert: context.flags.upsert === true,
|
|
8303
|
+
...progress === void 0 ? {} : { onProgress: progress.report },
|
|
8304
|
+
signal: interrupt.signal
|
|
8305
|
+
});
|
|
8306
|
+
} finally {
|
|
8307
|
+
progress?.close();
|
|
8308
|
+
caught = interrupt.settle();
|
|
8309
|
+
}
|
|
8310
|
+
try {
|
|
8311
|
+
const result = unwrap(answer);
|
|
8312
|
+
const to = `${NARO_SCHEME}${target.bucket}/${path}`;
|
|
8313
|
+
context.output.result(
|
|
8314
|
+
{ projectId, from: local, to, bytes: body.size, id: result.id },
|
|
8315
|
+
[
|
|
8316
|
+
`\u2705 Uploaded ${printable(local)} \u2192 ${printable(to)} (${binarySize(body.size)})`
|
|
8317
|
+
]
|
|
8318
|
+
);
|
|
8319
|
+
} finally {
|
|
8320
|
+
if (caught !== null) endWith(caught);
|
|
8321
|
+
}
|
|
8064
8322
|
}
|
|
8065
8323
|
var WINDOWS_DEVICE = /^(?:con|prn|aux|nul|com[1-9]|lpt[1-9])(?:\.|$)/i;
|
|
8066
8324
|
var MAX_FILE_NAME_BYTES = 255;
|
|
@@ -8073,6 +8331,62 @@ function savedAs(path) {
|
|
|
8073
8331
|
);
|
|
8074
8332
|
return name;
|
|
8075
8333
|
}
|
|
8334
|
+
var NETWORK_MESSAGE = "The request did not reach the Naro backend. Check the url and the network.";
|
|
8335
|
+
async function failureOf(response) {
|
|
8336
|
+
const { status } = response;
|
|
8337
|
+
const invalid = {
|
|
8338
|
+
code: "INVALID_RESPONSE",
|
|
8339
|
+
message: `The server did not answer like a Naro backend (HTTP ${String(status)}).`,
|
|
8340
|
+
details: { status }
|
|
8341
|
+
};
|
|
8342
|
+
let text;
|
|
8343
|
+
try {
|
|
8344
|
+
text = await response.text();
|
|
8345
|
+
} catch {
|
|
8346
|
+
return { code: "NETWORK_ERROR", message: NETWORK_MESSAGE };
|
|
8347
|
+
}
|
|
8348
|
+
let body;
|
|
8349
|
+
if (text.trim() !== "") {
|
|
8350
|
+
try {
|
|
8351
|
+
body = JSON.parse(text);
|
|
8352
|
+
} catch {
|
|
8353
|
+
return invalid;
|
|
8354
|
+
}
|
|
8355
|
+
}
|
|
8356
|
+
const error = typeof body === "object" && body !== null && "error" in body ? body.error : void 0;
|
|
8357
|
+
if (typeof error !== "object" || error === null) return invalid;
|
|
8358
|
+
const { code, message, details } = error;
|
|
8359
|
+
if (typeof code !== "string" || code === "" || typeof message !== "string")
|
|
8360
|
+
return invalid;
|
|
8361
|
+
return details === void 0 ? { code, message } : { code, message, details };
|
|
8362
|
+
}
|
|
8363
|
+
async function getObject(url, serviceKey) {
|
|
8364
|
+
let response;
|
|
8365
|
+
try {
|
|
8366
|
+
response = await fetch(url, {
|
|
8367
|
+
headers: { apikey: serviceKey },
|
|
8368
|
+
redirect: "error"
|
|
8369
|
+
});
|
|
8370
|
+
} catch {
|
|
8371
|
+
throw new NaroError("NETWORK_ERROR", NETWORK_MESSAGE);
|
|
8372
|
+
}
|
|
8373
|
+
if (response.ok) return response;
|
|
8374
|
+
return unwrap({ data: null, error: await failureOf(response) });
|
|
8375
|
+
}
|
|
8376
|
+
async function* chunksOf2(response, received) {
|
|
8377
|
+
if (response.body === null) return;
|
|
8378
|
+
try {
|
|
8379
|
+
for await (const chunk2 of Readable.fromWeb(
|
|
8380
|
+
response.body
|
|
8381
|
+
)) {
|
|
8382
|
+
const bytes = chunk2;
|
|
8383
|
+
received.bytes += bytes.byteLength;
|
|
8384
|
+
yield bytes;
|
|
8385
|
+
}
|
|
8386
|
+
} catch {
|
|
8387
|
+
throw new NaroError("NETWORK_ERROR", NETWORK_MESSAGE);
|
|
8388
|
+
}
|
|
8389
|
+
}
|
|
8076
8390
|
async function download(context, source, local) {
|
|
8077
8391
|
if (source.path === "" || source.path.endsWith("/"))
|
|
8078
8392
|
throw new NaroError(
|
|
@@ -8088,14 +8402,20 @@ async function download(context, source, local) {
|
|
|
8088
8402
|
"VALIDATION_FAILED",
|
|
8089
8403
|
`Cannot save to ${printable(local)}: its folder does not exist.`
|
|
8090
8404
|
);
|
|
8091
|
-
const
|
|
8092
|
-
|
|
8093
|
-
await
|
|
8405
|
+
const route2 = objectRoute(source.bucket, source.path);
|
|
8406
|
+
if (!route2.ok) throw new NaroError(route2.error.code, route2.error.message);
|
|
8407
|
+
const { projectId, url, serviceKey } = await objects(context);
|
|
8408
|
+
const response = await getObject(
|
|
8409
|
+
`${url}/_naro/v1/storage/object/${route2.encoded}`,
|
|
8410
|
+
serviceKey
|
|
8411
|
+
);
|
|
8412
|
+
const received = { bytes: 0 };
|
|
8413
|
+
await writeAtomically(chunksOf2(response, received), destination);
|
|
8094
8414
|
const from = `${NARO_SCHEME}${source.bucket}/${source.path}`;
|
|
8095
8415
|
context.output.result(
|
|
8096
|
-
{ projectId, from, to: destination, bytes:
|
|
8416
|
+
{ projectId, from, to: destination, bytes: received.bytes },
|
|
8097
8417
|
[
|
|
8098
|
-
`\u2705 Downloaded ${printable(from)} \u2192 ${printable(destination)} (${binarySize(
|
|
8418
|
+
`\u2705 Downloaded ${printable(from)} \u2192 ${printable(destination)} (${binarySize(received.bytes)})`
|
|
8099
8419
|
]
|
|
8100
8420
|
);
|
|
8101
8421
|
}
|
|
@@ -8388,8 +8708,9 @@ Usage:
|
|
|
8388
8708
|
naro storage ls [naro://<bucket>[/<prefix>]] [--recursive]
|
|
8389
8709
|
The buckets, or the folders and objects under a prefix
|
|
8390
8710
|
naro storage cp <from> <to> [--upsert] [--content-type <t>] [--cache-control <seconds>]
|
|
8391
|
-
One file between this machine and naro://<bucket>/<path>, 50 MiB
|
|
8711
|
+
One file between this machine and naro://<bucket>/<path>, up to the object limit (over 50 MiB in parts),
|
|
8392
8712
|
with the project's service key (NARO_SERVICE_KEY, or fetched with a warning).
|
|
8713
|
+
A big upload shows its progress on stderr; a file of 4 GiB or more cannot be uploaded yet.
|
|
8393
8714
|
A download replaces a local file of the same name
|
|
8394
8715
|
naro storage rm naro://<bucket>/<path>\u2026 [--recursive]
|
|
8395
8716
|
Remove objects; --recursive removes what is under a folder (asks first):
|
package/dist/output.d.ts
CHANGED
|
@@ -21,6 +21,11 @@ export declare class Output {
|
|
|
21
21
|
* whose stdout is the deliverable itself (`naro gen types > db.d.ts`).
|
|
22
22
|
*/
|
|
23
23
|
warn(message: string): void;
|
|
24
|
+
/**
|
|
25
|
+
* Text on stderr exactly as given, with no line break added: a progress
|
|
26
|
+
* line a terminal rewrites in place (`\r…`). Never stdout, in either mode.
|
|
27
|
+
*/
|
|
28
|
+
progress(text: string): void;
|
|
24
29
|
/** Extra diagnostics, only with --debug. */
|
|
25
30
|
debugLog(message: string): void;
|
|
26
31
|
/**
|
package/dist/upload.d.ts
CHANGED
|
@@ -8,4 +8,6 @@ export interface UploadOptions {
|
|
|
8
8
|
export declare function uploadArtifact(artifact: DeploymentArtifact, opts: UploadOptions): Promise<{
|
|
9
9
|
artifactKey: string;
|
|
10
10
|
previewUrl: string | null;
|
|
11
|
+
/** How many files, how big and how long the upload took. */
|
|
12
|
+
uploadSummary: string;
|
|
11
13
|
}>;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "naro-cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.16.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"bin": {
|
|
6
6
|
"naro": "./dist/index.js"
|
|
@@ -33,12 +33,12 @@
|
|
|
33
33
|
"tsx": "4.23.12",
|
|
34
34
|
"typescript": "7.0.2",
|
|
35
35
|
"vitest": "3.2.7",
|
|
36
|
-
"naro-js": "0.
|
|
36
|
+
"naro-js": "0.4.0",
|
|
37
37
|
"@next-deploy/core": "0.1.0",
|
|
38
38
|
"@next-deploy/data-core": "0.1.0",
|
|
39
39
|
"@next-deploy/sdk": "0.1.0",
|
|
40
|
-
"@next-deploy/
|
|
41
|
-
"@next-deploy/
|
|
40
|
+
"@next-deploy/build": "0.1.0",
|
|
41
|
+
"@next-deploy/config": "0.1.0"
|
|
42
42
|
},
|
|
43
43
|
"scripts": {
|
|
44
44
|
"build": "tsup && pnpm build:types",
|