naro-cli 0.16.0 → 0.17.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 -3
- package/dist/{chunk-XNT7SINN.js → chunk-PYRN5XR6.js} +177 -24
- package/dist/deploy.js +1 -1
- package/dist/index.js +193 -19
- package/package.json +5 -5
package/README.md
CHANGED
|
@@ -88,7 +88,8 @@ naro env remove DATABASE_URL
|
|
|
88
88
|
naro env pull [.env.local]
|
|
89
89
|
|
|
90
90
|
naro domains list
|
|
91
|
-
naro domains add www.example.com Prints the
|
|
91
|
+
naro domains add www.example.com Prints the CNAME to create; naro connects
|
|
92
|
+
it within ~5 minutes of the record appearing
|
|
92
93
|
naro domains add example.com Apex: also connects www.example.com and
|
|
93
94
|
redirects the apex to it (--no-www to skip)
|
|
94
95
|
naro domains verify [host] Re-check DNS now and print what is missing
|
|
@@ -190,8 +191,9 @@ naro rollback [deployment-id] Back to the previous production build
|
|
|
190
191
|
```
|
|
191
192
|
|
|
192
193
|
`naro domains list` reads the stored status; `naro domains verify` is the one
|
|
193
|
-
that asks
|
|
194
|
-
|
|
194
|
+
that asks the DNS and Cloudflare again — and registers the domain on the spot
|
|
195
|
+
once its CNAME points at naro — so it is what turns a stuck "pending" into the
|
|
196
|
+
reason it is stuck. It exits non-zero while a domain is not live —
|
|
195
197
|
with or without `--wait` — so CI can gate on it:
|
|
196
198
|
|
|
197
199
|
```bash
|
|
@@ -203,6 +205,15 @@ An apex (`example.com`) is added as a pair by default because most registrars
|
|
|
203
205
|
goes on `www.example.com` and the apex answers with a 308 redirect to it.
|
|
204
206
|
`--no-www` registers the apex alone.
|
|
205
207
|
|
|
208
|
+
A domain another naro project already has is not refused. `naro domains add`
|
|
209
|
+
fails with `DOMAIN_VERIFICATION_REQUIRED`, and the message names a TXT record:
|
|
210
|
+
`_naro-verify.<host>` with the value `naro-verify=<token>`. Create it, then run
|
|
211
|
+
`naro domains verify <host>`. Once the record is visible, the domain moves to
|
|
212
|
+
this project. After that, the CNAME is added as usual. Add the TXT record
|
|
213
|
+
before the CNAME: until the move, the other project still serves the domain.
|
|
214
|
+
The check lasts 7 days. `naro domains list` shows open checks with their
|
|
215
|
+
expiry, and adding the domain again prints the same token.
|
|
216
|
+
|
|
206
217
|
`naro db dump` asks Cloudflare for the dump and streams it straight from
|
|
207
218
|
Cloudflare's storage to the file — the bytes never pass through naro. While the
|
|
208
219
|
export runs, Cloudflare holds every other query to the database, so dump a busy
|
|
@@ -793,6 +804,27 @@ An `--update-check` you give replaces the default: without
|
|
|
793
804
|
|
|
794
805
|
## Release notes
|
|
795
806
|
|
|
807
|
+
### 0.17.0
|
|
808
|
+
|
|
809
|
+
- `naro domains add` prints one CNAME record and no TXT records. naro
|
|
810
|
+
registers the domain once the CNAME points at it, usually within about five
|
|
811
|
+
minutes. The status line reads `Waiting for DNS — add the CNAME above; naro
|
|
812
|
+
connects it within about five minutes.` `naro domains verify` registers it at
|
|
813
|
+
once.
|
|
814
|
+
- A domain another project holds now gets an ownership check instead of
|
|
815
|
+
`DOMAIN_EXISTS` (see the `naro domains` notes under Commands). `naro domains verify <host>`
|
|
816
|
+
reads the TXT record and moves the domain here. `naro domains list` shows
|
|
817
|
+
open checks under `Ownership checks (n):`. Against a control plane that
|
|
818
|
+
predates ownership checks, list shows none.
|
|
819
|
+
- A control plane without custom domains set up answers domain changes with
|
|
820
|
+
`503 DOMAINS_NOT_CONFIGURED` (`Custom domains are not configured on this
|
|
821
|
+
server.`), and the CLI prints that message. Before, the CLI got a 500.
|
|
822
|
+
- `naro deploy` builds Next.js apps that use npm faster:
|
|
823
|
+
- A second build candidate copies the installed packages instead of
|
|
824
|
+
installing them again.
|
|
825
|
+
- npm's audit and funding notices are off during the build. A project
|
|
826
|
+
`.npmrc` that turns audit on still wins.
|
|
827
|
+
|
|
796
828
|
### 0.16.0
|
|
797
829
|
|
|
798
830
|
- `naro storage cp` uploads a file up to the object limit, under 4 GiB. Over
|
|
@@ -5,9 +5,9 @@ import {
|
|
|
5
5
|
|
|
6
6
|
// src/deploy.ts
|
|
7
7
|
import { existsSync as existsSync7 } from "fs";
|
|
8
|
-
import { cp as cp2, mkdtemp as mkdtemp2, readFile as readFile13, rm as
|
|
8
|
+
import { cp as cp2, mkdtemp as mkdtemp2, readFile as readFile13, rm as rm4 } from "fs/promises";
|
|
9
9
|
import { tmpdir as tmpdir2 } from "os";
|
|
10
|
-
import { basename as basename3, dirname as
|
|
10
|
+
import { basename as basename3, dirname as dirname5, join as join24, resolve as resolve10 } from "path";
|
|
11
11
|
import { fileURLToPath } from "url";
|
|
12
12
|
|
|
13
13
|
// ../../packages/build/src/artifact-upload.ts
|
|
@@ -102,7 +102,7 @@ var ERROR_CODE = {
|
|
|
102
102
|
deps: "INSTALL_FAILED",
|
|
103
103
|
next: "BUILD_FAILED"
|
|
104
104
|
};
|
|
105
|
-
async function computeDepsCacheKey(checkoutDir, packageManager) {
|
|
105
|
+
async function computeDepsCacheKey(checkoutDir, packageManager, extra = []) {
|
|
106
106
|
for (const name of LOCKFILES) {
|
|
107
107
|
let content;
|
|
108
108
|
try {
|
|
@@ -110,7 +110,10 @@ async function computeDepsCacheKey(checkoutDir, packageManager) {
|
|
|
110
110
|
} catch {
|
|
111
111
|
continue;
|
|
112
112
|
}
|
|
113
|
-
const
|
|
113
|
+
const hasher = createHash("sha256").update(name).update("\n").update(content);
|
|
114
|
+
if (extra.length > 0) hasher.update(`
|
|
115
|
+
${extra.join("\n")}`);
|
|
116
|
+
const hash2 = hasher.digest("hex");
|
|
114
117
|
return `${packageManager}-${hash2}`;
|
|
115
118
|
}
|
|
116
119
|
return null;
|
|
@@ -262,9 +265,9 @@ function hashAsset(data, contentType2) {
|
|
|
262
265
|
}
|
|
263
266
|
|
|
264
267
|
// ../../packages/build/src/local-build-executor.ts
|
|
265
|
-
import { access as access8, mkdtemp, readFile as readFile12, rm as
|
|
268
|
+
import { access as access8, mkdtemp, readFile as readFile12, rm as rm3 } from "fs/promises";
|
|
266
269
|
import { tmpdir } from "os";
|
|
267
|
-
import { dirname as
|
|
270
|
+
import { dirname as dirname4, join as join17, resolve as resolve7 } from "path";
|
|
268
271
|
|
|
269
272
|
// ../../packages/core/src/route-patterns.ts
|
|
270
273
|
var ROUTE_PATTERN = /^\/[A-Za-z0-9\-._~%!$&()*+,;=:@/[\]]*$/;
|
|
@@ -15302,7 +15305,11 @@ function cacheEnv(dir) {
|
|
|
15302
15305
|
function withSharedCache(spec, dir) {
|
|
15303
15306
|
return {
|
|
15304
15307
|
...spec,
|
|
15305
|
-
addArgs: (specs) => [
|
|
15308
|
+
addArgs: (specs) => [
|
|
15309
|
+
...spec.addArgs(specs),
|
|
15310
|
+
...spec.sharedCacheAddArgs ?? [],
|
|
15311
|
+
...spec.cacheArgs(dir)
|
|
15312
|
+
],
|
|
15306
15313
|
addEnv: { ...spec.addEnv, ...cacheEnv(dir) }
|
|
15307
15314
|
};
|
|
15308
15315
|
}
|
|
@@ -15343,7 +15350,24 @@ async function detectPackageManager(root) {
|
|
|
15343
15350
|
install: ["npm", "ci"],
|
|
15344
15351
|
cacheArgs: (dir) => ["--cache", dir],
|
|
15345
15352
|
// --no-save keeps package.json and the lockfile untouched.
|
|
15346
|
-
addArgs: (specs) => ["npm", "install", "--no-save", ...specs]
|
|
15353
|
+
addArgs: (specs) => ["npm", "install", "--no-save", ...specs],
|
|
15354
|
+
// --prefer-offline takes package metadata from the restored cache instead
|
|
15355
|
+
// of asking the registry again for each of the toolchain's ~160 packages
|
|
15356
|
+
// (npm keeps metadata in the same cache, which the deps cache saves).
|
|
15357
|
+
// Measured on one vCPU like the build container: 8.8 s → 7.9 s locally,
|
|
15358
|
+
// and the live add took 17 s against 8.8 s here — the gap is registry
|
|
15359
|
+
// round trips.
|
|
15360
|
+
//
|
|
15361
|
+
// npm trusts cached metadata outright: a cached packument that lacks the
|
|
15362
|
+
// asked-for version fails with ETARGET instead of being fetched again
|
|
15363
|
+
// (pacote leaves that revalidation as a TODO). That cannot happen in the
|
|
15364
|
+
// build's own cache because its key carries the toolchain
|
|
15365
|
+
// (computeDepsCacheKey): a toolchain bump starts from an empty cache, and
|
|
15366
|
+
// the cache restored under a key was filled by this same add over the
|
|
15367
|
+
// same lockfile. A machine's default cache has no such key, so without
|
|
15368
|
+
// the build cache (no zstd, no transport — mostly the CLI) the add stays
|
|
15369
|
+
// online (PR #134 review).
|
|
15370
|
+
sharedCacheAddArgs: ["--prefer-offline"]
|
|
15347
15371
|
};
|
|
15348
15372
|
}
|
|
15349
15373
|
if (await exists(join2(root, "yarn.lock"))) {
|
|
@@ -15691,14 +15715,18 @@ async function pathExists(path) {
|
|
|
15691
15715
|
return false;
|
|
15692
15716
|
}
|
|
15693
15717
|
}
|
|
15718
|
+
var BUILD_NPMRC = [
|
|
15719
|
+
"# Host package-manager credentials are intentionally unavailable.",
|
|
15720
|
+
"audit=false",
|
|
15721
|
+
"fund=false",
|
|
15722
|
+
"update-notifier=false",
|
|
15723
|
+
""
|
|
15724
|
+
].join("\n");
|
|
15694
15725
|
async function createPackageManagerConfig(appRoot, writer = async (path, contents) => {
|
|
15695
15726
|
await writeFile3(path, contents, "utf8");
|
|
15696
15727
|
}) {
|
|
15697
15728
|
const packageManagerConfigPath = join5(appRoot, ".next-deploy-user.npmrc");
|
|
15698
|
-
await writer(
|
|
15699
|
-
packageManagerConfigPath,
|
|
15700
|
-
"# Host package-manager credentials are intentionally unavailable.\n"
|
|
15701
|
-
);
|
|
15729
|
+
await writer(packageManagerConfigPath, BUILD_NPMRC);
|
|
15702
15730
|
return packageManagerConfigPath;
|
|
15703
15731
|
}
|
|
15704
15732
|
async function createPlatformBuildConfig(appRoot, deploymentId, writer = async (path, contents) => {
|
|
@@ -17964,8 +17992,11 @@ function createVinextCloudflareStrategy(options) {
|
|
|
17964
17992
|
}
|
|
17965
17993
|
|
|
17966
17994
|
// ../../packages/build/src/strategy/workspace.ts
|
|
17967
|
-
import {
|
|
17968
|
-
import {
|
|
17995
|
+
import { execFile } from "child_process";
|
|
17996
|
+
import { cp, mkdir as mkdir2, readdir as readdir4, rm as rm2 } from "fs/promises";
|
|
17997
|
+
import { basename as basename2, dirname as dirname3, join as join16, relative as relative5, resolve as resolve6 } from "path";
|
|
17998
|
+
import { promisify } from "util";
|
|
17999
|
+
var execFileAsync = promisify(execFile);
|
|
17969
18000
|
var SKIPPED_ANYWHERE = /* @__PURE__ */ new Set(["node_modules"]);
|
|
17970
18001
|
function shouldCopy(path) {
|
|
17971
18002
|
const name = basename2(path);
|
|
@@ -17980,6 +18011,33 @@ async function copyCheckout(source, target2) {
|
|
|
17980
18011
|
filter: (path) => resolve6(path) === origin || shouldCopy(path)
|
|
17981
18012
|
});
|
|
17982
18013
|
}
|
|
18014
|
+
async function copyInstalledPackages(source, target2) {
|
|
18015
|
+
const origin = resolve6(source);
|
|
18016
|
+
const installs = [];
|
|
18017
|
+
const walk2 = async (dir) => {
|
|
18018
|
+
for (const entry of await readdir4(dir, { withFileTypes: true })) {
|
|
18019
|
+
if (!entry.isDirectory() || entry.name === ".git") continue;
|
|
18020
|
+
const path = join16(dir, entry.name);
|
|
18021
|
+
if (entry.name === "node_modules") installs.push(path);
|
|
18022
|
+
else await walk2(path);
|
|
18023
|
+
}
|
|
18024
|
+
};
|
|
18025
|
+
await walk2(origin);
|
|
18026
|
+
for (const install of installs)
|
|
18027
|
+
await copyTree(install, join16(target2, relative5(origin, install)));
|
|
18028
|
+
}
|
|
18029
|
+
async function copyTree(from, to) {
|
|
18030
|
+
await mkdir2(dirname3(to), { recursive: true });
|
|
18031
|
+
if (process.platform !== "win32") {
|
|
18032
|
+
try {
|
|
18033
|
+
await execFileAsync("cp", ["-a", from, to]);
|
|
18034
|
+
return;
|
|
18035
|
+
} catch {
|
|
18036
|
+
await rm2(to, { recursive: true, force: true });
|
|
18037
|
+
}
|
|
18038
|
+
}
|
|
18039
|
+
await cp(from, to, { recursive: true, verbatimSymlinks: true });
|
|
18040
|
+
}
|
|
17983
18041
|
function forkedAppRoot(checkout, appRoot, forkCheckout) {
|
|
17984
18042
|
return join16(forkCheckout, relative5(checkout, appRoot));
|
|
17985
18043
|
}
|
|
@@ -18004,7 +18062,7 @@ async function withRouteList(artifact, list, appRoot, observer) {
|
|
|
18004
18062
|
}
|
|
18005
18063
|
var defaultLocalBuildDependencies = {
|
|
18006
18064
|
createWorkspace: () => mkdtemp(join17(tmpdir(), "next-deploy-build-")),
|
|
18007
|
-
removeWorkspace: (path) =>
|
|
18065
|
+
removeWorkspace: (path) => rm3(path, { recursive: true, force: true }),
|
|
18008
18066
|
runCommand,
|
|
18009
18067
|
detectPackageManager,
|
|
18010
18068
|
resolveAppRoot,
|
|
@@ -18024,6 +18082,7 @@ var defaultLocalBuildDependencies = {
|
|
|
18024
18082
|
readVinextArtifact,
|
|
18025
18083
|
vinextCommand: "vinext",
|
|
18026
18084
|
forkWorkspace: copyCheckout,
|
|
18085
|
+
copyInstalledPackages,
|
|
18027
18086
|
collectProjectFacts,
|
|
18028
18087
|
listNextRoutes,
|
|
18029
18088
|
hashFingerprintInputs: hashStrategyInputs,
|
|
@@ -18045,7 +18104,7 @@ async function readInstalledNextVersion(appRoot, checkout) {
|
|
|
18045
18104
|
} catch {
|
|
18046
18105
|
}
|
|
18047
18106
|
if (dir === stop) return null;
|
|
18048
|
-
const parent =
|
|
18107
|
+
const parent = dirname4(dir);
|
|
18049
18108
|
if (parent === dir) return null;
|
|
18050
18109
|
dir = parent;
|
|
18051
18110
|
}
|
|
@@ -18263,7 +18322,11 @@ var LocalBuildExecutor = class {
|
|
|
18263
18322
|
let depsKey = null;
|
|
18264
18323
|
let depsRestored = false;
|
|
18265
18324
|
if (cachePorts) {
|
|
18266
|
-
depsKey = await computeDepsCacheKey(
|
|
18325
|
+
depsKey = await computeDepsCacheKey(
|
|
18326
|
+
checkout,
|
|
18327
|
+
packageManager.name,
|
|
18328
|
+
isNext ? VINEXT_TOOLCHAIN : []
|
|
18329
|
+
);
|
|
18267
18330
|
if (depsKey) {
|
|
18268
18331
|
depsRestored = await tryRestore(
|
|
18269
18332
|
"deps",
|
|
@@ -18348,6 +18411,7 @@ var LocalBuildExecutor = class {
|
|
|
18348
18411
|
const fork = this.dependencies.forkWorkspace ?? defaultFork;
|
|
18349
18412
|
await fork(checkout, pristine);
|
|
18350
18413
|
let forks = 0;
|
|
18414
|
+
const resolverConcurrency = this.dependencies.resolverConcurrency ?? 1;
|
|
18351
18415
|
const forkWorkspace = async (label2) => {
|
|
18352
18416
|
const target2 = join17(
|
|
18353
18417
|
workspace,
|
|
@@ -18355,7 +18419,9 @@ var LocalBuildExecutor = class {
|
|
|
18355
18419
|
`${String(forks++)}-${label2.replace(/[^a-z0-9-]/gi, "-")}`
|
|
18356
18420
|
);
|
|
18357
18421
|
await fork(pristine, target2);
|
|
18358
|
-
|
|
18422
|
+
if (packageManager.name === "npm" && resolverConcurrency === 1)
|
|
18423
|
+
await (this.dependencies.copyInstalledPackages ?? copyInstalledPackages)(checkout, target2);
|
|
18424
|
+
else await install(target2);
|
|
18359
18425
|
return {
|
|
18360
18426
|
checkout: target2,
|
|
18361
18427
|
appRoot: forkedAppRoot(checkout, appRoot, target2)
|
|
@@ -18398,7 +18464,7 @@ var LocalBuildExecutor = class {
|
|
|
18398
18464
|
{
|
|
18399
18465
|
pinned: input.strategy ?? null,
|
|
18400
18466
|
fingerprint: fingerprint.hash,
|
|
18401
|
-
concurrency:
|
|
18467
|
+
concurrency: resolverConcurrency,
|
|
18402
18468
|
...this.dependencies.now ? { now: this.dependencies.now } : {}
|
|
18403
18469
|
}
|
|
18404
18470
|
);
|
|
@@ -19533,6 +19599,31 @@ var DomainsApi = class {
|
|
|
19533
19599
|
const match = await this.findByHostname(projectId, hostname3);
|
|
19534
19600
|
return this.verify(projectId, match.id, options);
|
|
19535
19601
|
}
|
|
19602
|
+
/**
|
|
19603
|
+
* Ownership checks the project opened for domains another project holds
|
|
19604
|
+
* (known-issues #156). Adding such a domain fails with
|
|
19605
|
+
* DOMAIN_VERIFICATION_REQUIRED and its check in `details.claim`.
|
|
19606
|
+
*/
|
|
19607
|
+
async listClaims(projectId) {
|
|
19608
|
+
const body = await this.client.request(
|
|
19609
|
+
`/api/projects/${encodeURIComponent(projectId)}/domain-claims`,
|
|
19610
|
+
{ context: "List ownership checks" }
|
|
19611
|
+
);
|
|
19612
|
+
return body.claims;
|
|
19613
|
+
}
|
|
19614
|
+
/** Reads the TXT record; when it matches, the domain moves to this project. */
|
|
19615
|
+
async verifyClaim(projectId, claimId) {
|
|
19616
|
+
return this.client.request(
|
|
19617
|
+
`/api/projects/${encodeURIComponent(projectId)}/domain-claims/${encodeURIComponent(claimId)}/verify`,
|
|
19618
|
+
{ method: "POST", body: {}, context: "Verify domain ownership" }
|
|
19619
|
+
);
|
|
19620
|
+
}
|
|
19621
|
+
async removeClaim(projectId, claimId) {
|
|
19622
|
+
await this.client.request(
|
|
19623
|
+
`/api/projects/${encodeURIComponent(projectId)}/domain-claims/${encodeURIComponent(claimId)}`,
|
|
19624
|
+
{ method: "DELETE", context: "Remove ownership check" }
|
|
19625
|
+
);
|
|
19626
|
+
}
|
|
19536
19627
|
async removeById(projectId, domainId) {
|
|
19537
19628
|
await this.client.request(
|
|
19538
19629
|
`/api/projects/${encodeURIComponent(projectId)}/domains/${encodeURIComponent(domainId)}`,
|
|
@@ -20002,6 +20093,14 @@ function bucketFields(fields, into = {}) {
|
|
|
20002
20093
|
if (fields[key] !== void 0) into[key] = fields[key];
|
|
20003
20094
|
return into;
|
|
20004
20095
|
}
|
|
20096
|
+
function listFields(options) {
|
|
20097
|
+
const body = {};
|
|
20098
|
+
if (options.prefix !== void 0) body.prefix = options.prefix;
|
|
20099
|
+
if (options.limit !== void 0) body.limit = options.limit;
|
|
20100
|
+
if (options.offset !== void 0) body.offset = options.offset;
|
|
20101
|
+
if (options.sortBy !== void 0) body.sortBy = options.sortBy;
|
|
20102
|
+
return body;
|
|
20103
|
+
}
|
|
20005
20104
|
var BackendApi = class {
|
|
20006
20105
|
constructor(client) {
|
|
20007
20106
|
this.client = client;
|
|
@@ -20526,6 +20625,60 @@ var StorageApi = class {
|
|
|
20526
20625
|
);
|
|
20527
20626
|
return { deleted: body.deleted, done: body.done };
|
|
20528
20627
|
}
|
|
20628
|
+
/**
|
|
20629
|
+
* What is directly below a folder of a bucket — its folders first, by
|
|
20630
|
+
* name, then its files — through the control plane's object proxy
|
|
20631
|
+
* (Storage 3-1e): the project's Naro worker lists it with the stored
|
|
20632
|
+
* service key, so every object is listed whatever the policies say. There
|
|
20633
|
+
* is no search. Needs `storage.manage`, Storage on and the backend on and
|
|
20634
|
+
* ready (`STORAGE_NOT_ENABLED`, `BACKEND_OFF`, `BACKEND_NOT_READY`, 409);
|
|
20635
|
+
* the worker's refusals — `BUCKET_NOT_FOUND`, `INVALID_REQUEST` with
|
|
20636
|
+
* `details.path` and `details.reason` — come through as they are.
|
|
20637
|
+
*/
|
|
20638
|
+
async listObjects(projectId, bucket, options = {}) {
|
|
20639
|
+
const body = await this.client.request(
|
|
20640
|
+
this.bucketPath(projectId, bucket, "/objects/list"),
|
|
20641
|
+
{ method: "POST", body: listFields(options), context: "List objects" }
|
|
20642
|
+
);
|
|
20643
|
+
return body.objects;
|
|
20644
|
+
}
|
|
20645
|
+
/**
|
|
20646
|
+
* A link that downloads one object without a key, for `expiresIn` seconds
|
|
20647
|
+
* (1–3600 through the control plane; `VALIDATION_FAILED` past it), on the
|
|
20648
|
+
* project's URL. Replacing or deleting the object ends it. The path is
|
|
20649
|
+
* held to the object name rules (`INVALID_PATH`); one not there is the
|
|
20650
|
+
* worker's `OBJECT_NOT_FOUND`.
|
|
20651
|
+
*/
|
|
20652
|
+
async signObject(projectId, bucket, path, options) {
|
|
20653
|
+
const body = await this.client.request(
|
|
20654
|
+
this.bucketPath(projectId, bucket, "/objects/sign"),
|
|
20655
|
+
{
|
|
20656
|
+
method: "POST",
|
|
20657
|
+
body: {
|
|
20658
|
+
path,
|
|
20659
|
+
expiresIn: options.expiresIn,
|
|
20660
|
+
// false is no download (supabase-js): left out, since the worker
|
|
20661
|
+
// answers it 400 INVALID_REQUEST — it is not a file name.
|
|
20662
|
+
...options.download === void 0 || options.download === false ? {} : { download: options.download }
|
|
20663
|
+
},
|
|
20664
|
+
context: "Sign an object URL"
|
|
20665
|
+
}
|
|
20666
|
+
);
|
|
20667
|
+
return { signedUrl: body.signedUrl };
|
|
20668
|
+
}
|
|
20669
|
+
/**
|
|
20670
|
+
* Deletes up to 1,000 objects by their full paths and answers the ones it
|
|
20671
|
+
* deleted, in the order asked: a path not there, or being written, is left
|
|
20672
|
+
* out — not an error (supabase-js's `remove`). A folder is not deleted by
|
|
20673
|
+
* its name; list it and delete what is in it.
|
|
20674
|
+
*/
|
|
20675
|
+
async removeObjects(projectId, bucket, prefixes) {
|
|
20676
|
+
const body = await this.client.request(
|
|
20677
|
+
this.bucketPath(projectId, bucket, "/objects"),
|
|
20678
|
+
{ method: "DELETE", body: { prefixes }, context: "Remove objects" }
|
|
20679
|
+
);
|
|
20680
|
+
return body.objects;
|
|
20681
|
+
}
|
|
20529
20682
|
};
|
|
20530
20683
|
|
|
20531
20684
|
// ../../packages/sdk/src/database.ts
|
|
@@ -28135,14 +28288,14 @@ async function promptForMissingEnv(ctx, keys) {
|
|
|
28135
28288
|
}
|
|
28136
28289
|
|
|
28137
28290
|
// src/git-info.ts
|
|
28138
|
-
import { execFile } from "child_process";
|
|
28291
|
+
import { execFile as execFile2 } from "child_process";
|
|
28139
28292
|
var SUPPORTED_REMOTE = [
|
|
28140
28293
|
/^https:\/\/[^\s]+$/,
|
|
28141
28294
|
/^ssh:\/\/[^\s]+$/,
|
|
28142
28295
|
/^git@[A-Za-z0-9.-]+:[A-Za-z0-9._/-]+(\.git)?$/
|
|
28143
28296
|
];
|
|
28144
28297
|
var defaultExec = (args, cwd) => new Promise((resolve11, reject) => {
|
|
28145
|
-
|
|
28298
|
+
execFile2("git", args, { cwd }, (error51, stdout) => {
|
|
28146
28299
|
if (error51) reject(error51);
|
|
28147
28300
|
else resolve11(stdout);
|
|
28148
28301
|
});
|
|
@@ -28385,11 +28538,11 @@ async function projectName(sourceDir) {
|
|
|
28385
28538
|
}
|
|
28386
28539
|
var COPY_SKIP = /(^|[\\/])(node_modules|\.next|\.open-next|\.git|\.wrangler|\.turbo|\.naro)([\\/]|$)/;
|
|
28387
28540
|
function resolveBin(name) {
|
|
28388
|
-
let dir =
|
|
28541
|
+
let dir = dirname5(fileURLToPath(import.meta.url));
|
|
28389
28542
|
for (; ; ) {
|
|
28390
28543
|
const candidate = join24(dir, "node_modules", ".bin", name);
|
|
28391
28544
|
if (existsSync7(candidate)) return candidate;
|
|
28392
|
-
const parent =
|
|
28545
|
+
const parent = dirname5(dir);
|
|
28393
28546
|
if (parent === dir) return name;
|
|
28394
28547
|
dir = parent;
|
|
28395
28548
|
}
|
|
@@ -28415,7 +28568,7 @@ async function detectPackageManagerLenient(root) {
|
|
|
28415
28568
|
function localDependencies(sourceDir) {
|
|
28416
28569
|
return {
|
|
28417
28570
|
createWorkspace: () => mkdtemp2(join24(tmpdir2(), "naro-build-")),
|
|
28418
|
-
removeWorkspace: (path) =>
|
|
28571
|
+
removeWorkspace: (path) => rm4(path, { recursive: true, force: true }),
|
|
28419
28572
|
async runCommand(spec, observer) {
|
|
28420
28573
|
if (spec.command === "git" && spec.args[0] === "clone") {
|
|
28421
28574
|
const checkout = spec.args.at(-1);
|
package/dist/deploy.js
CHANGED
package/dist/index.js
CHANGED
|
@@ -82,7 +82,7 @@ import {
|
|
|
82
82
|
toNaroError,
|
|
83
83
|
unreadablePolicyComment,
|
|
84
84
|
writeProjectLink
|
|
85
|
-
} from "./chunk-
|
|
85
|
+
} from "./chunk-PYRN5XR6.js";
|
|
86
86
|
import "./chunk-K3NQKI34.js";
|
|
87
87
|
|
|
88
88
|
// src/args.ts
|
|
@@ -4560,21 +4560,69 @@ function domainLines(domain) {
|
|
|
4560
4560
|
}
|
|
4561
4561
|
return lines;
|
|
4562
4562
|
}
|
|
4563
|
+
function claimLines(claim) {
|
|
4564
|
+
return [
|
|
4565
|
+
` \u23F3 ${claim.hostname} \u2014 another project holds it. Publish this TXT record, then run \`naro domains verify ${claim.hostname}\`:`,
|
|
4566
|
+
` TXT ${claim.record.name}`,
|
|
4567
|
+
` host: ${claim.record.host}`,
|
|
4568
|
+
` value: ${claim.record.value}`
|
|
4569
|
+
];
|
|
4570
|
+
}
|
|
4571
|
+
async function openClaims(client, projectId) {
|
|
4572
|
+
try {
|
|
4573
|
+
return await client.domains.listClaims(projectId);
|
|
4574
|
+
} catch (error) {
|
|
4575
|
+
if (isNaroError(error) && error.code === "NOT_FOUND") return [];
|
|
4576
|
+
throw error;
|
|
4577
|
+
}
|
|
4578
|
+
}
|
|
4563
4579
|
async function runDomainsList(context) {
|
|
4564
4580
|
const client = await context.client();
|
|
4565
4581
|
const projectId = await context.resolveProjectId();
|
|
4566
4582
|
const domains = await client.domains.list(projectId);
|
|
4583
|
+
const claims = await openClaims(client, projectId);
|
|
4567
4584
|
const pending = domains.filter((domain) => !isReady(domain));
|
|
4568
4585
|
context.output.result(
|
|
4569
|
-
{ projectId, domains },
|
|
4570
|
-
domains.length === 0 ? ["No custom domains. Add one with `naro domains add example.com`."] : [
|
|
4571
|
-
|
|
4572
|
-
|
|
4586
|
+
{ projectId, domains, claims },
|
|
4587
|
+
domains.length === 0 && claims.length === 0 ? ["No custom domains. Add one with `naro domains add example.com`."] : [
|
|
4588
|
+
...domains.length > 0 ? [
|
|
4589
|
+
`Domains (${String(domains.length)}):`,
|
|
4590
|
+
...domains.flatMap(domainLines)
|
|
4591
|
+
] : [],
|
|
4592
|
+
...claims.length > 0 ? [
|
|
4593
|
+
...domains.length > 0 ? [""] : [],
|
|
4594
|
+
`Ownership checks (${String(claims.length)}):`,
|
|
4595
|
+
...claims.flatMap(claimLines)
|
|
4596
|
+
] : [],
|
|
4573
4597
|
// list reads our stored copy; only verify goes and looks again.
|
|
4574
4598
|
...pending.length > 0 ? ["", "Re-check DNS now: naro domains verify"] : []
|
|
4575
4599
|
]
|
|
4576
4600
|
);
|
|
4577
4601
|
}
|
|
4602
|
+
async function verifyClaimFor(context, client, projectId, hostname) {
|
|
4603
|
+
const wanted = hostname.toLowerCase().replace(/\.+$/, "");
|
|
4604
|
+
const claim = (await openClaims(client, projectId)).find(
|
|
4605
|
+
(candidate) => candidate.hostname === wanted
|
|
4606
|
+
);
|
|
4607
|
+
if (!claim) return false;
|
|
4608
|
+
const result = await client.domains.verifyClaim(projectId, claim.id);
|
|
4609
|
+
if (!result.verified) {
|
|
4610
|
+
const cause = result.claim.cause;
|
|
4611
|
+
throw new NaroError(
|
|
4612
|
+
cause?.code ?? "DOMAIN_VERIFICATION_REQUIRED",
|
|
4613
|
+
cause ? `${cause.message}${cause.fix ? ` ${cause.fix}` : ""}` : `${wanted} is not verified yet.`,
|
|
4614
|
+
{ details: { claim: result.claim } }
|
|
4615
|
+
);
|
|
4616
|
+
}
|
|
4617
|
+
context.output.result({ projectId, verified: true, domain: result.domain }, [
|
|
4618
|
+
`\u2705 Ownership of ${result.domain.hostname} verified \u2014 it now belongs to this project.`,
|
|
4619
|
+
"",
|
|
4620
|
+
...domainLines(result.domain),
|
|
4621
|
+
"",
|
|
4622
|
+
"Status: Waiting for DNS \u2014 add the CNAME above; naro connects it within about five minutes. Check with `naro domains verify`."
|
|
4623
|
+
]);
|
|
4624
|
+
return true;
|
|
4625
|
+
}
|
|
4578
4626
|
async function runDomainsAdd(context) {
|
|
4579
4627
|
const [hostname] = context.positionalOrThrow(
|
|
4580
4628
|
1,
|
|
@@ -4608,7 +4656,7 @@ async function runDomainsAdd(context) {
|
|
|
4608
4656
|
` refuses the record above, run \`naro domains add ${alternative}\`.`
|
|
4609
4657
|
] : [],
|
|
4610
4658
|
"",
|
|
4611
|
-
isReady(domain) ? "The domain is active." : "Status:
|
|
4659
|
+
isReady(domain) ? "The domain is active." : "Status: Waiting for DNS \u2014 add the CNAME above; naro connects it within about five minutes. Check with `naro domains verify`."
|
|
4612
4660
|
]
|
|
4613
4661
|
);
|
|
4614
4662
|
}
|
|
@@ -4632,7 +4680,14 @@ async function runDomainsVerify(context, overrides = {}) {
|
|
|
4632
4680
|
const progress = (line) => {
|
|
4633
4681
|
if (!context.flags.json) context.output.log(line);
|
|
4634
4682
|
};
|
|
4635
|
-
let domains
|
|
4683
|
+
let domains;
|
|
4684
|
+
try {
|
|
4685
|
+
domains = await verifyAll(client, projectId, hostname);
|
|
4686
|
+
} catch (error) {
|
|
4687
|
+
if (hostname !== void 0 && isNaroError(error) && error.code === "DOMAIN_NOT_FOUND" && await verifyClaimFor(context, client, projectId, hostname))
|
|
4688
|
+
return;
|
|
4689
|
+
throw error;
|
|
4690
|
+
}
|
|
4636
4691
|
if (domains.length === 0) {
|
|
4637
4692
|
context.output.result({ projectId, domains, ready: false }, [
|
|
4638
4693
|
"No custom domains. Add one with `naro domains add example.com`."
|
|
@@ -4691,7 +4746,7 @@ async function runDomainsVerify(context, overrides = {}) {
|
|
|
4691
4746
|
const cause = notReady[0]?.cause;
|
|
4692
4747
|
throw new NaroError(
|
|
4693
4748
|
cause?.code ?? "DOMAIN_NOT_READY",
|
|
4694
|
-
`${names} is not live yet.${cause ? ` ${cause.message}` : ""}
|
|
4749
|
+
`${names} is not live yet.${cause ? ` ${cause.message}` : ""} naro connects it within about five minutes of the CNAME appearing; \`naro domains verify --wait\` waits for it.`,
|
|
4695
4750
|
{ details }
|
|
4696
4751
|
);
|
|
4697
4752
|
}
|
|
@@ -5674,7 +5729,10 @@ async function call(config, options) {
|
|
|
5674
5729
|
}
|
|
5675
5730
|
}
|
|
5676
5731
|
const token = options.withToken ? await config.tokens.get() : null;
|
|
5677
|
-
const headers = {
|
|
5732
|
+
const headers = {
|
|
5733
|
+
...options.headers,
|
|
5734
|
+
apikey: config.apiKey
|
|
5735
|
+
};
|
|
5678
5736
|
if (token !== null) headers.Authorization = `Bearer ${token}`;
|
|
5679
5737
|
const init = {
|
|
5680
5738
|
method: options.method,
|
|
@@ -7363,6 +7421,7 @@ async function readJson(response, guard) {
|
|
|
7363
7421
|
return dataOf2(response.status, body, guard);
|
|
7364
7422
|
}
|
|
7365
7423
|
var ROUTE = "/storage/upload/multipart";
|
|
7424
|
+
var UPLOAD_TOKEN_HEADER = "x-naro-upload-token";
|
|
7366
7425
|
var CONCURRENCY = 4;
|
|
7367
7426
|
var ATTEMPTS = 4;
|
|
7368
7427
|
var BACKOFF_MS = [1e3, 2e3, 4e3];
|
|
@@ -7393,6 +7452,8 @@ var sleepFor = (ms, signal) => new Promise((done) => {
|
|
|
7393
7452
|
async function uploadInParts(a) {
|
|
7394
7453
|
const { signal } = a;
|
|
7395
7454
|
const sleep2 = a.sleep ?? sleepFor;
|
|
7455
|
+
const signed = a.uploadToken === void 0 ? {} : { [UPLOAD_TOKEN_HEADER]: a.uploadToken };
|
|
7456
|
+
const withToken = a.uploadToken === void 0;
|
|
7396
7457
|
if (signal?.aborted) return fail(uploadAborted());
|
|
7397
7458
|
const started = await call(a.http, {
|
|
7398
7459
|
method: a.method,
|
|
@@ -7406,7 +7467,8 @@ async function uploadInParts(a) {
|
|
|
7406
7467
|
// PUT always replaces: the worker refuses `upsert` on it.
|
|
7407
7468
|
...a.method === "POST" && a.upsert ? { upsert: true } : {}
|
|
7408
7469
|
},
|
|
7409
|
-
|
|
7470
|
+
headers: signed,
|
|
7471
|
+
withToken
|
|
7410
7472
|
});
|
|
7411
7473
|
if (started.error !== null) return fail(started.error);
|
|
7412
7474
|
const plan = dataOf2(
|
|
@@ -7426,9 +7488,12 @@ async function uploadInParts(a) {
|
|
|
7426
7488
|
}
|
|
7427
7489
|
};
|
|
7428
7490
|
const callOff = async () => {
|
|
7429
|
-
await call(a.http, {
|
|
7430
|
-
|
|
7431
|
-
|
|
7491
|
+
await call(a.http, {
|
|
7492
|
+
method: "DELETE",
|
|
7493
|
+
path: base,
|
|
7494
|
+
headers: signed,
|
|
7495
|
+
withToken
|
|
7496
|
+
}).catch(() => void 0);
|
|
7432
7497
|
};
|
|
7433
7498
|
const etags = [];
|
|
7434
7499
|
const failure = {
|
|
@@ -7443,9 +7508,9 @@ async function uploadInParts(a) {
|
|
|
7443
7508
|
const sent = await send(a.http, {
|
|
7444
7509
|
method: "PUT",
|
|
7445
7510
|
path: `${base}/${String(n)}`,
|
|
7446
|
-
headers: { "content-type": "application/octet-stream" },
|
|
7511
|
+
headers: { "content-type": "application/octet-stream", ...signed },
|
|
7447
7512
|
body: bytes,
|
|
7448
|
-
withToken
|
|
7513
|
+
withToken,
|
|
7449
7514
|
signal
|
|
7450
7515
|
});
|
|
7451
7516
|
if (sent.error !== null) return sent;
|
|
@@ -7495,7 +7560,8 @@ async function uploadInParts(a) {
|
|
|
7495
7560
|
body: {
|
|
7496
7561
|
parts: etags.map((etag, i) => ({ partNumber: i + 1, etag }))
|
|
7497
7562
|
},
|
|
7498
|
-
|
|
7563
|
+
headers: signed,
|
|
7564
|
+
withToken
|
|
7499
7565
|
});
|
|
7500
7566
|
if (completed.error !== null) {
|
|
7501
7567
|
if (completed.answer !== null && completed.error.code !== "INVALID_RESPONSE")
|
|
@@ -7509,7 +7575,8 @@ var MAX_SINGLE_UPLOAD_BYTES2 = 52428800;
|
|
|
7509
7575
|
var STORAGE_MESSAGES = {
|
|
7510
7576
|
bucket: "The bucket name is not valid.",
|
|
7511
7577
|
request: "The storage request is not valid.",
|
|
7512
|
-
body: "upload takes a Blob, File, ArrayBuffer, typed array or string."
|
|
7578
|
+
body: "upload takes a Blob, File, ArrayBuffer, typed array or string.",
|
|
7579
|
+
signature: "The signed URL is not valid."
|
|
7513
7580
|
};
|
|
7514
7581
|
var RESERVED_BUCKETS = /* @__PURE__ */ new Set([
|
|
7515
7582
|
"authenticated",
|
|
@@ -7658,9 +7725,22 @@ function payloadOf(body, contentType) {
|
|
|
7658
7725
|
};
|
|
7659
7726
|
}
|
|
7660
7727
|
var SIGNED_PREFIX = "/_naro/v1/storage/object/sign/";
|
|
7728
|
+
var SIGNED_UPLOAD_PREFIX = "/_naro/v1/storage/object/upload/sign/";
|
|
7729
|
+
var TOKEN_TEXT = /^[\x21-\x7e]+$/;
|
|
7730
|
+
var TOKEN_PARAMETER = /([?&]token=)[^&#\s"'<>]*/g;
|
|
7731
|
+
function withoutToken(error, token) {
|
|
7732
|
+
const { details } = error;
|
|
7733
|
+
if (!isRecord(details) || typeof details.cause !== "string") return error;
|
|
7734
|
+
let cause = details.cause.replace(TOKEN_PARAMETER, "$1[redacted]");
|
|
7735
|
+
for (const form of [encodeURIComponent(token), token])
|
|
7736
|
+
cause = cause.replaceAll(form, "[redacted]");
|
|
7737
|
+
return { ...error, details: { ...details, cause } };
|
|
7738
|
+
}
|
|
7661
7739
|
var isFileObject = (value) => isRecord(value) && typeof value.id === "string" && typeof value.name === "string" && isRecord(value.metadata);
|
|
7662
7740
|
var isEntry2 = (value) => isRecord(value) && typeof value.name === "string" && (value.id === null || isFileObject(value));
|
|
7663
7741
|
var isSigned = (value) => isRecord(value) && typeof value.signedUrl === "string" && value.signedUrl.startsWith(SIGNED_PREFIX);
|
|
7742
|
+
var isSignedUpload = (value) => isRecord(value) && typeof value.signedUrl === "string" && value.signedUrl.startsWith(SIGNED_UPLOAD_PREFIX) && typeof value.token === "string" && value.token !== "" && typeof value.path === "string";
|
|
7743
|
+
var partsBody = (body) => isArrayBuffer(body) ? new Uint8Array(body) : body;
|
|
7664
7744
|
function listOf(guard) {
|
|
7665
7745
|
return (value) => Array.isArray(value) && value.every(guard);
|
|
7666
7746
|
}
|
|
@@ -7708,8 +7788,7 @@ var StorageFileApi = class {
|
|
|
7708
7788
|
method,
|
|
7709
7789
|
bucket: this.#bucket,
|
|
7710
7790
|
path,
|
|
7711
|
-
|
|
7712
|
-
body: isArrayBuffer(payload.body) ? new Uint8Array(payload.body) : payload.body,
|
|
7791
|
+
body: partsBody(payload.body),
|
|
7713
7792
|
size: payload.size,
|
|
7714
7793
|
contentType: payload.type,
|
|
7715
7794
|
cacheControl: cache,
|
|
@@ -7837,6 +7916,101 @@ var StorageFileApi = class {
|
|
|
7837
7916
|
error: null
|
|
7838
7917
|
};
|
|
7839
7918
|
}
|
|
7919
|
+
/**
|
|
7920
|
+
* A token that lets whoever holds it upload `path` for two hours (Storage
|
|
7921
|
+
* 3-1e-2), as supabase-js's: made where the key is — a server with the
|
|
7922
|
+
* service key for any path, anon or a signed-in user only where the
|
|
7923
|
+
* policies let them upload. With `upsert: true` it may replace a file
|
|
7924
|
+
* that is there.
|
|
7925
|
+
* The token goes to `uploadToSignedUrl`; `signedUrl` is where a client
|
|
7926
|
+
* without naro-js PUTs the bytes.
|
|
7927
|
+
*/
|
|
7928
|
+
async createSignedUploadUrl(path, options = {}) {
|
|
7929
|
+
const target = this.#objectPath("upload/sign/", path);
|
|
7930
|
+
if (!target.ok) return fail(target.error);
|
|
7931
|
+
const upsert = (options ?? {}).upsert === true;
|
|
7932
|
+
const signed = await this.#json(
|
|
7933
|
+
{
|
|
7934
|
+
method: "POST",
|
|
7935
|
+
path: target.path,
|
|
7936
|
+
body: upsert ? { upsert: true } : {}
|
|
7937
|
+
},
|
|
7938
|
+
isSignedUpload
|
|
7939
|
+
);
|
|
7940
|
+
if (signed.error !== null) return signed;
|
|
7941
|
+
return {
|
|
7942
|
+
data: {
|
|
7943
|
+
signedUrl: `${this.#http.baseUrl}${signed.data.signedUrl}`,
|
|
7944
|
+
token: signed.data.token,
|
|
7945
|
+
path: signed.data.path
|
|
7946
|
+
},
|
|
7947
|
+
error: null
|
|
7948
|
+
};
|
|
7949
|
+
}
|
|
7950
|
+
/**
|
|
7951
|
+
* Uploads `body` to `path` with a token from `createSignedUploadUrl`
|
|
7952
|
+
* (Storage 3-1e-2). No session is sent, or needed: the token is the
|
|
7953
|
+
* permission, so a page's anon-key client uploads where only a server
|
|
7954
|
+
* could sign. It works until the token expires, two hours from when it
|
|
7955
|
+
* was made; one made without `upsert` uploads one new file
|
|
7956
|
+
* (`OBJECT_EXISTS` for a name that is there). A body over 50 MiB goes in
|
|
7957
|
+
* parts with the token on every step, and once started may finish after
|
|
7958
|
+
* the two hours. `contentType`, `cacheControl`, `onProgress` and `signal`
|
|
7959
|
+
* are `upload`'s.
|
|
7960
|
+
*/
|
|
7961
|
+
async uploadToSignedUrl(path, token, body, options = {}) {
|
|
7962
|
+
const given = options ?? {};
|
|
7963
|
+
const target = this.#objectPath("upload/sign/", path);
|
|
7964
|
+
if (!target.ok) return fail(target.error);
|
|
7965
|
+
if (typeof token !== "string" || !TOKEN_TEXT.test(token))
|
|
7966
|
+
return fail({
|
|
7967
|
+
code: "INVALID_SIGNATURE",
|
|
7968
|
+
message: STORAGE_MESSAGES.signature
|
|
7969
|
+
});
|
|
7970
|
+
const payload = payloadOf(body, given.contentType);
|
|
7971
|
+
if (!payload.ok) return fail(payload.error);
|
|
7972
|
+
const cache = `max-age=${given.cacheControl ?? "3600"}`;
|
|
7973
|
+
const unreadable = headerProblem(given.contentType, cache);
|
|
7974
|
+
if (unreadable !== null) return fail(unreadable);
|
|
7975
|
+
const { signal } = given;
|
|
7976
|
+
if (signal?.aborted) return fail(uploadAborted());
|
|
7977
|
+
const uploaded = payload.size > MAX_SINGLE_UPLOAD_BYTES2 ? await uploadInParts({
|
|
7978
|
+
http: this.#http,
|
|
7979
|
+
method: "POST",
|
|
7980
|
+
bucket: this.#bucket,
|
|
7981
|
+
path,
|
|
7982
|
+
body: partsBody(payload.body),
|
|
7983
|
+
size: payload.size,
|
|
7984
|
+
contentType: payload.type,
|
|
7985
|
+
cacheControl: cache,
|
|
7986
|
+
upsert: false,
|
|
7987
|
+
uploadToken: token,
|
|
7988
|
+
...given.onProgress ? { onProgress: given.onProgress } : {},
|
|
7989
|
+
signal
|
|
7990
|
+
}) : await this.#putSigned(target.path, token, payload, cache, signal);
|
|
7991
|
+
if (uploaded.error !== null) return fail(uploaded.error);
|
|
7992
|
+
return {
|
|
7993
|
+
data: { path: uploaded.data.path, fullPath: uploaded.data.fullPath },
|
|
7994
|
+
error: null
|
|
7995
|
+
};
|
|
7996
|
+
}
|
|
7997
|
+
/**
|
|
7998
|
+
* The one-request signed upload: the token in the query, no session — it
|
|
7999
|
+
* is the permission. A failure's cause never carries the token.
|
|
8000
|
+
*/
|
|
8001
|
+
async #putSigned(path, token, payload, cache, signal) {
|
|
8002
|
+
const sent = await send(this.#http, {
|
|
8003
|
+
method: "PUT",
|
|
8004
|
+
path: `${path}?token=${encodeURIComponent(token)}`,
|
|
8005
|
+
headers: { "content-type": payload.type, "cache-control": cache },
|
|
8006
|
+
body: payload.body,
|
|
8007
|
+
withToken: false,
|
|
8008
|
+
signal
|
|
8009
|
+
});
|
|
8010
|
+
if (sent.error !== null) return fail(withoutToken(sent.error, token));
|
|
8011
|
+
const read = await readJson(sent.response, isUploadResult);
|
|
8012
|
+
return read.error === null ? read : fail(withoutToken(read.error, token));
|
|
8013
|
+
}
|
|
7840
8014
|
/**
|
|
7841
8015
|
* A public bucket's URL for the object. No request is made, so whether
|
|
7842
8016
|
* the bucket is public is not checked (as in supabase-js); a path no URL
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "naro-cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.17.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.5.0",
|
|
37
|
+
"@next-deploy/config": "0.1.0",
|
|
37
38
|
"@next-deploy/core": "0.1.0",
|
|
38
|
-
"@next-deploy/data-core": "0.1.0",
|
|
39
|
-
"@next-deploy/sdk": "0.1.0",
|
|
40
39
|
"@next-deploy/build": "0.1.0",
|
|
41
|
-
"@next-deploy/
|
|
40
|
+
"@next-deploy/data-core": "0.1.0",
|
|
41
|
+
"@next-deploy/sdk": "0.1.0"
|
|
42
42
|
},
|
|
43
43
|
"scripts": {
|
|
44
44
|
"build": "tsup && pnpm build:types",
|