naro-cli 0.5.3 → 0.6.1
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 +14 -0
- package/dist/{chunk-ZHVBIQBE.js → chunk-WKJELBOU.js} +331 -52
- package/dist/deploy.d.ts +5 -1
- package/dist/deploy.js +1 -1
- package/dist/git-info.d.ts +10 -0
- package/dist/index.js +4 -1
- package/package.json +4 -4
package/README.md
CHANGED
|
@@ -27,6 +27,20 @@ Values you register are encrypted at rest, and `naro env pull` /
|
|
|
27
27
|
`naro env list --reveal` can read them back — your account credentials are
|
|
28
28
|
effectively your env credentials.
|
|
29
29
|
|
|
30
|
+
## Build cache
|
|
31
|
+
|
|
32
|
+
Deploys reuse the dependency and framework caches from your previous build,
|
|
33
|
+
which are stored per project and restored before the install step. The
|
|
34
|
+
archives are zstd-compressed, so caching needs `zstd` on your PATH:
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
brew install zstd # macOS
|
|
38
|
+
apt install zstd # Debian/Ubuntu
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
Without it deploys still work — they just build from scratch every time and
|
|
42
|
+
say so in the build log.
|
|
43
|
+
|
|
30
44
|
## Commands
|
|
31
45
|
|
|
32
46
|
```text
|
|
@@ -9,7 +9,7 @@ var __export = (target, all) => {
|
|
|
9
9
|
import { existsSync as existsSync6 } from "fs";
|
|
10
10
|
import { cp, mkdtemp as mkdtemp2, readFile as readFile7, rm as rm2 } from "fs/promises";
|
|
11
11
|
import { tmpdir as tmpdir2 } from "os";
|
|
12
|
-
import { basename, dirname, join as join15 } from "path";
|
|
12
|
+
import { basename, dirname, join as join15, resolve as resolve4 } from "path";
|
|
13
13
|
import { fileURLToPath } from "url";
|
|
14
14
|
|
|
15
15
|
// ../../packages/build/src/artifact-upload.ts
|
|
@@ -92,19 +92,43 @@ async function computeDepsCacheKey(checkoutDir, packageManager) {
|
|
|
92
92
|
async function log(observer, scope, level, message) {
|
|
93
93
|
await observer.onLog({ level, stage: STAGE[scope], message });
|
|
94
94
|
}
|
|
95
|
+
async function reportUnavailable(scope, ports, observer) {
|
|
96
|
+
if (ports.report?.unavailable === true) return;
|
|
97
|
+
if (ports.report) ports.report.unavailable = true;
|
|
98
|
+
await log(
|
|
99
|
+
observer,
|
|
100
|
+
scope,
|
|
101
|
+
"WARN",
|
|
102
|
+
"Build cache is unavailable - this build runs uncached and saves nothing for the next one."
|
|
103
|
+
);
|
|
104
|
+
}
|
|
95
105
|
async function restoreCache(args) {
|
|
96
106
|
const { scope, key, targetDir, ports, observer } = args;
|
|
97
107
|
const urls = await ports.transport.resolveUrls(scope, key);
|
|
98
|
-
if (!urls)
|
|
108
|
+
if (!urls) {
|
|
109
|
+
await reportUnavailable(scope, ports, observer);
|
|
110
|
+
return false;
|
|
111
|
+
}
|
|
99
112
|
const response = await ports.fetch(urls.getUrl);
|
|
100
|
-
if (response.status !== 200)
|
|
113
|
+
if (response.status !== 200) {
|
|
114
|
+
await log(
|
|
115
|
+
observer,
|
|
116
|
+
scope,
|
|
117
|
+
"INFO",
|
|
118
|
+
`No ${scope} cache stored for ${key} yet.`
|
|
119
|
+
);
|
|
120
|
+
return false;
|
|
121
|
+
}
|
|
101
122
|
const archive = ports.tmpFile(".tar.zst");
|
|
102
123
|
await writeFile(archive, Buffer.from(await response.arrayBuffer()));
|
|
103
124
|
await mkdir(targetDir, { recursive: true });
|
|
104
125
|
await ports.runCommand(
|
|
105
126
|
{
|
|
106
127
|
command: "tar",
|
|
107
|
-
|
|
128
|
+
// --zstd, not --use-compress-program: BSD tar (macOS, where the CLI
|
|
129
|
+
// builds) feeds the external program wrong on extract and dies with
|
|
130
|
+
// "Broken pipe". Both GNU tar 1.31+ and bsdtar 3.x accept --zstd.
|
|
131
|
+
args: ["--zstd", "-xf", archive, "-C", targetDir],
|
|
108
132
|
cwd: targetDir,
|
|
109
133
|
timeoutMs: 12e4,
|
|
110
134
|
stage: STAGE[scope],
|
|
@@ -119,22 +143,26 @@ async function saveCache(args) {
|
|
|
119
143
|
const { scope, key, sourceDir, ports, observer } = args;
|
|
120
144
|
try {
|
|
121
145
|
const info2 = await stat(sourceDir);
|
|
122
|
-
if (!info2.isDirectory())
|
|
146
|
+
if (!info2.isDirectory()) throw new Error("not a directory");
|
|
123
147
|
} catch {
|
|
148
|
+
await log(
|
|
149
|
+
observer,
|
|
150
|
+
scope,
|
|
151
|
+
"WARN",
|
|
152
|
+
`Nothing to save for the ${scope} cache - the build wrote nothing to that directory.`
|
|
153
|
+
);
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
const urls = await ports.transport.resolveUrls(scope, key);
|
|
157
|
+
if (!urls) {
|
|
158
|
+
await reportUnavailable(scope, ports, observer);
|
|
124
159
|
return;
|
|
125
160
|
}
|
|
126
161
|
const archive = ports.tmpFile(".tar.zst");
|
|
127
162
|
await ports.runCommand(
|
|
128
163
|
{
|
|
129
164
|
command: "tar",
|
|
130
|
-
args: [
|
|
131
|
-
"--use-compress-program=zstd",
|
|
132
|
-
"-cf",
|
|
133
|
-
archive,
|
|
134
|
-
"-C",
|
|
135
|
-
sourceDir,
|
|
136
|
-
"."
|
|
137
|
-
],
|
|
165
|
+
args: ["--zstd", "-cf", archive, "-C", sourceDir, "."],
|
|
138
166
|
cwd: sourceDir,
|
|
139
167
|
timeoutMs: 12e4,
|
|
140
168
|
stage: STAGE[scope],
|
|
@@ -142,8 +170,6 @@ async function saveCache(args) {
|
|
|
142
170
|
},
|
|
143
171
|
observer
|
|
144
172
|
);
|
|
145
|
-
const urls = await ports.transport.resolveUrls(scope, key);
|
|
146
|
-
if (!urls) return;
|
|
147
173
|
const body = await readFile(archive);
|
|
148
174
|
const response = await ports.fetch(urls.putUrl, { method: "PUT", body });
|
|
149
175
|
if (!response.ok) {
|
|
@@ -158,10 +184,45 @@ async function saveCache(args) {
|
|
|
158
184
|
await log(observer, scope, "INFO", `Saved ${scope} cache (${key}).`);
|
|
159
185
|
}
|
|
160
186
|
|
|
187
|
+
// ../../packages/build/src/cache-transport.ts
|
|
188
|
+
function createCacheTransport(options) {
|
|
189
|
+
return {
|
|
190
|
+
async resolveUrls(scope, key) {
|
|
191
|
+
try {
|
|
192
|
+
const url2 = new URL(
|
|
193
|
+
`/api/internal/deployments/${options.deploymentId}/cache/url`,
|
|
194
|
+
options.controlPlaneUrl
|
|
195
|
+
);
|
|
196
|
+
const response = await options.fetch(url2, {
|
|
197
|
+
method: "POST",
|
|
198
|
+
headers: {
|
|
199
|
+
"Content-Type": "application/json",
|
|
200
|
+
Authorization: `Bearer ${options.callbackToken}`
|
|
201
|
+
},
|
|
202
|
+
body: JSON.stringify({ scope, key })
|
|
203
|
+
});
|
|
204
|
+
if (!response.ok) return null;
|
|
205
|
+
const data = await response.json();
|
|
206
|
+
if (data.disabled === true) return null;
|
|
207
|
+
if (typeof data.getUrl !== "string" || typeof data.putUrl !== "string") {
|
|
208
|
+
return null;
|
|
209
|
+
}
|
|
210
|
+
return { getUrl: data.getUrl, putUrl: data.putUrl };
|
|
211
|
+
} catch {
|
|
212
|
+
return null;
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
|
|
161
218
|
// ../../packages/build/src/hash.ts
|
|
162
219
|
import { createHash as createHash2 } from "crypto";
|
|
163
|
-
|
|
164
|
-
|
|
220
|
+
var FALLBACK_CONTENT_TYPE = "application/octet-stream";
|
|
221
|
+
function assetContentType(value) {
|
|
222
|
+
return value || FALLBACK_CONTENT_TYPE;
|
|
223
|
+
}
|
|
224
|
+
function hashAsset(data, contentType2) {
|
|
225
|
+
return createHash2("sha256").update(assetContentType(contentType2)).update("\0").update(data).digest("hex").slice(0, 32);
|
|
165
226
|
}
|
|
166
227
|
|
|
167
228
|
// ../../packages/build/src/local-build-executor.ts
|
|
@@ -173,6 +234,16 @@ import { join as join8 } from "path";
|
|
|
173
234
|
var MAX_FILE_BYTES = 25 * 1024 * 1024;
|
|
174
235
|
var MAX_ARTIFACT_BYTES = 64 * 1024 * 1024;
|
|
175
236
|
|
|
237
|
+
// ../../packages/core/src/deployment.ts
|
|
238
|
+
var PHASES = [
|
|
239
|
+
"QUEUED",
|
|
240
|
+
"CLONING",
|
|
241
|
+
"INSTALLING",
|
|
242
|
+
"BUILDING",
|
|
243
|
+
"DEPLOYING"
|
|
244
|
+
];
|
|
245
|
+
var ACTIVE_STATUSES = new Set(PHASES);
|
|
246
|
+
|
|
176
247
|
// ../../packages/core/src/errors.ts
|
|
177
248
|
var DeploymentError = class extends Error {
|
|
178
249
|
code;
|
|
@@ -14843,20 +14914,28 @@ async function detectPackageManager(root) {
|
|
|
14843
14914
|
if (await exists(join2(root, "pnpm-lock.yaml"))) {
|
|
14844
14915
|
return {
|
|
14845
14916
|
name: "pnpm",
|
|
14846
|
-
install: ["pnpm", "install", "--frozen-lockfile"]
|
|
14917
|
+
install: ["pnpm", "install", "--frozen-lockfile"],
|
|
14918
|
+
cacheArgs: (dir) => ["--store-dir", dir]
|
|
14847
14919
|
};
|
|
14848
14920
|
}
|
|
14849
14921
|
if (await exists(join2(root, "package-lock.json"))) {
|
|
14850
|
-
return {
|
|
14922
|
+
return {
|
|
14923
|
+
name: "npm",
|
|
14924
|
+
install: ["npm", "ci"],
|
|
14925
|
+
cacheArgs: (dir) => ["--cache", dir]
|
|
14926
|
+
};
|
|
14851
14927
|
}
|
|
14852
14928
|
if (await exists(join2(root, "yarn.lock"))) {
|
|
14929
|
+
const modern = await usesModernYarn(root);
|
|
14853
14930
|
return {
|
|
14854
14931
|
name: "yarn",
|
|
14855
14932
|
install: [
|
|
14856
14933
|
"yarn",
|
|
14857
14934
|
"install",
|
|
14858
|
-
|
|
14859
|
-
]
|
|
14935
|
+
modern ? "--immutable" : "--frozen-lockfile"
|
|
14936
|
+
],
|
|
14937
|
+
// Berry takes no install-time cache flag; YARN_CACHE_FOLDER covers it.
|
|
14938
|
+
cacheArgs: (dir) => modern ? [] : ["--cache-folder", dir]
|
|
14860
14939
|
};
|
|
14861
14940
|
}
|
|
14862
14941
|
throw new DeploymentError(
|
|
@@ -15178,21 +15257,39 @@ function contentType(path) {
|
|
|
15178
15257
|
return "application/javascript";
|
|
15179
15258
|
case ".json":
|
|
15180
15259
|
return "application/json";
|
|
15260
|
+
case ".map":
|
|
15261
|
+
return "application/json";
|
|
15262
|
+
case ".webmanifest":
|
|
15263
|
+
return "application/manifest+json";
|
|
15264
|
+
case ".txt":
|
|
15265
|
+
return "text/plain; charset=utf-8";
|
|
15266
|
+
case ".xml":
|
|
15267
|
+
return "application/xml";
|
|
15181
15268
|
case ".svg":
|
|
15182
15269
|
return "image/svg+xml";
|
|
15183
15270
|
case ".wasm":
|
|
15184
15271
|
return "application/wasm";
|
|
15272
|
+
case ".ico":
|
|
15273
|
+
return "image/x-icon";
|
|
15185
15274
|
case ".png":
|
|
15186
15275
|
return "image/png";
|
|
15187
15276
|
case ".jpg":
|
|
15188
15277
|
case ".jpeg":
|
|
15189
15278
|
return "image/jpeg";
|
|
15279
|
+
case ".gif":
|
|
15280
|
+
return "image/gif";
|
|
15190
15281
|
case ".webp":
|
|
15191
15282
|
return "image/webp";
|
|
15283
|
+
case ".avif":
|
|
15284
|
+
return "image/avif";
|
|
15192
15285
|
case ".woff":
|
|
15193
15286
|
return "font/woff";
|
|
15194
15287
|
case ".woff2":
|
|
15195
15288
|
return "font/woff2";
|
|
15289
|
+
case ".ttf":
|
|
15290
|
+
return "font/ttf";
|
|
15291
|
+
case ".otf":
|
|
15292
|
+
return "font/otf";
|
|
15196
15293
|
default:
|
|
15197
15294
|
return "application/octet-stream";
|
|
15198
15295
|
}
|
|
@@ -15567,8 +15664,27 @@ var defaultDependencies = {
|
|
|
15567
15664
|
openNextCommand: "opennextjs-cloudflare",
|
|
15568
15665
|
wranglerCommand: "wrangler",
|
|
15569
15666
|
cacheFetch: (input, init) => fetch(input, init),
|
|
15570
|
-
convertProxyToMiddleware
|
|
15667
|
+
convertProxyToMiddleware,
|
|
15668
|
+
commandAvailable: hasCommand
|
|
15571
15669
|
};
|
|
15670
|
+
async function hasCommand(command) {
|
|
15671
|
+
try {
|
|
15672
|
+
await runCommand(
|
|
15673
|
+
{
|
|
15674
|
+
command,
|
|
15675
|
+
args: ["--version"],
|
|
15676
|
+
cwd: tmpdir(),
|
|
15677
|
+
timeoutMs: 1e4,
|
|
15678
|
+
stage: "CLONING",
|
|
15679
|
+
errorCode: "CLONE_FAILED"
|
|
15680
|
+
},
|
|
15681
|
+
{ onLog: () => void 0 }
|
|
15682
|
+
);
|
|
15683
|
+
return true;
|
|
15684
|
+
} catch {
|
|
15685
|
+
return false;
|
|
15686
|
+
}
|
|
15687
|
+
}
|
|
15572
15688
|
function sanitizeCacheKey(value) {
|
|
15573
15689
|
return value.replace(/[^A-Za-z0-9._-]/g, "-").slice(0, 128);
|
|
15574
15690
|
}
|
|
@@ -15610,14 +15726,25 @@ var LocalBuildExecutor = class {
|
|
|
15610
15726
|
const checkout = join8(workspace, "checkout");
|
|
15611
15727
|
const pmCacheDir = join8(workspace, ".pm-store");
|
|
15612
15728
|
let cacheSeq = 0;
|
|
15613
|
-
const
|
|
15729
|
+
const compressor = "zstd";
|
|
15730
|
+
const canCompress = cache ? await (this.dependencies.commandAvailable ?? hasCommand)(compressor) : false;
|
|
15731
|
+
const cachePorts = cache && canCompress ? {
|
|
15614
15732
|
transport: cache,
|
|
15615
15733
|
fetch: this.dependencies.cacheFetch ?? ((url2, init) => fetch(url2, init)),
|
|
15616
15734
|
runCommand: this.dependencies.runCommand,
|
|
15617
|
-
tmpFile: (suffix) => join8(workspace, `cache-${String(cacheSeq++)}${suffix}`)
|
|
15735
|
+
tmpFile: (suffix) => join8(workspace, `cache-${String(cacheSeq++)}${suffix}`),
|
|
15736
|
+
// One latch for the whole build so a disabled cache warns once.
|
|
15737
|
+
report: { unavailable: false }
|
|
15618
15738
|
} : null;
|
|
15619
15739
|
try {
|
|
15620
15740
|
await notifyStatus(observer, "CLONING");
|
|
15741
|
+
if (cache && !canCompress) {
|
|
15742
|
+
await observer.onLog({
|
|
15743
|
+
level: "WARN",
|
|
15744
|
+
stage: "CLONING",
|
|
15745
|
+
message: `Build cache disabled: ${compressor} is not installed. Install it to reuse dependencies between deploys.`
|
|
15746
|
+
});
|
|
15747
|
+
}
|
|
15621
15748
|
await this.dependencies.runCommand(
|
|
15622
15749
|
{
|
|
15623
15750
|
command: "git",
|
|
@@ -15729,7 +15856,7 @@ var LocalBuildExecutor = class {
|
|
|
15729
15856
|
await this.dependencies.runCommand(
|
|
15730
15857
|
{
|
|
15731
15858
|
command: installCommand,
|
|
15732
|
-
args: installArgs,
|
|
15859
|
+
args: cachePorts ? [...installArgs, ...packageManager.cacheArgs(pmCacheDir)] : installArgs,
|
|
15733
15860
|
cwd: checkout,
|
|
15734
15861
|
env: {
|
|
15735
15862
|
NODE_ENV: "development",
|
|
@@ -15956,13 +16083,154 @@ function controlPlaneUrl(env = process.env) {
|
|
|
15956
16083
|
|
|
15957
16084
|
// ../../packages/sdk/src/callback-server.ts
|
|
15958
16085
|
import { createServer } from "http";
|
|
15959
|
-
|
|
15960
|
-
|
|
16086
|
+
|
|
16087
|
+
// ../../packages/sdk/src/callback-page.ts
|
|
16088
|
+
function escapeHtml(value) {
|
|
16089
|
+
return value.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
16090
|
+
}
|
|
16091
|
+
var FAVICON = `data:image/svg+xml,${encodeURIComponent(
|
|
16092
|
+
'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32"><rect width="32" height="32" rx="8" fill="#ededed"/><text x="16" y="23" text-anchor="middle" fill="#000" font-family="system-ui,sans-serif" font-size="20" font-weight="700">n</text></svg>'
|
|
16093
|
+
)}`;
|
|
16094
|
+
var CHECK_ICON = '<svg viewBox="0 0 24 24" fill="none" aria-hidden="true"><path d="M5 12.5 10 17.5 19 7.5" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"/></svg>';
|
|
16095
|
+
var CROSS_ICON = '<svg viewBox="0 0 24 24" fill="none" aria-hidden="true"><path d="M7 7 17 17M17 7 7 17" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"/></svg>';
|
|
16096
|
+
var STYLES = `
|
|
16097
|
+
*{box-sizing:border-box}
|
|
16098
|
+
:root{
|
|
16099
|
+
--bg:#000;--surface:#0a0a0a;--surface-2:#111;--border:#232323;
|
|
16100
|
+
--text:#ededed;--muted:#a1a1a1;--faint:#6f6f6f;
|
|
16101
|
+
--sans:ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;
|
|
16102
|
+
--mono:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;
|
|
16103
|
+
}
|
|
16104
|
+
html{color-scheme:dark;-webkit-font-smoothing:antialiased}
|
|
16105
|
+
body{
|
|
16106
|
+
margin:0;min-height:100vh;display:grid;place-items:center;padding:24px;
|
|
16107
|
+
background:var(--bg);color:var(--text);font-family:var(--sans);
|
|
16108
|
+
font-size:14px;line-height:1.5;letter-spacing:-.006em;
|
|
16109
|
+
}
|
|
16110
|
+
body::before{
|
|
16111
|
+
content:"";position:fixed;inset:0;pointer-events:none;
|
|
16112
|
+
background:radial-gradient(62% 46% at 50% 0%,var(--glow),transparent 72%);
|
|
16113
|
+
}
|
|
16114
|
+
.card{
|
|
16115
|
+
position:relative;width:100%;max-width:400px;padding:34px 32px 28px;
|
|
16116
|
+
background:var(--surface);border:1px solid var(--border);border-radius:14px;
|
|
16117
|
+
text-align:center;animation:rise .4s cubic-bezier(.2,.7,.3,1) both;
|
|
16118
|
+
}
|
|
16119
|
+
.brand{
|
|
16120
|
+
display:inline-flex;align-items:center;gap:8px;
|
|
16121
|
+
font-size:14px;font-weight:600;letter-spacing:-.02em;color:var(--muted);
|
|
16122
|
+
}
|
|
16123
|
+
.brand-mark{
|
|
16124
|
+
display:inline-grid;width:22px;height:22px;place-items:center;
|
|
16125
|
+
background:var(--text);color:#000;border-radius:6px;
|
|
16126
|
+
font-size:12px;font-weight:700;line-height:1;
|
|
16127
|
+
}
|
|
16128
|
+
.glyph{
|
|
16129
|
+
width:52px;height:52px;margin:26px auto 20px;display:grid;place-items:center;
|
|
16130
|
+
border-radius:50%;color:var(--tone);
|
|
16131
|
+
background:var(--tone-soft);border:1px solid var(--tone-edge);
|
|
16132
|
+
}
|
|
16133
|
+
.glyph svg{width:26px;height:26px}
|
|
16134
|
+
.glyph path{stroke-dasharray:32;stroke-dashoffset:32;animation:draw .5s .15s cubic-bezier(.2,.7,.3,1) forwards}
|
|
16135
|
+
h1{margin:0 0 8px;font-size:19px;font-weight:600;letter-spacing:-.02em}
|
|
16136
|
+
p{margin:0 auto;max-width:32ch;color:var(--muted)}
|
|
16137
|
+
.detail{
|
|
16138
|
+
margin-top:18px;padding:10px 12px;text-align:left;word-break:break-word;
|
|
16139
|
+
background:var(--surface-2);border:1px solid var(--border);border-radius:8px;
|
|
16140
|
+
font-family:var(--mono);font-size:12.5px;color:var(--muted);
|
|
16141
|
+
}
|
|
16142
|
+
.hint{
|
|
16143
|
+
display:flex;align-items:center;gap:10px;margin-top:22px;padding:10px 12px;
|
|
16144
|
+
background:var(--surface-2);border:1px solid var(--border);border-radius:8px;text-align:left;
|
|
16145
|
+
}
|
|
16146
|
+
.hint-label{
|
|
16147
|
+
flex:none;font-size:10px;font-weight:600;letter-spacing:.09em;color:var(--faint);
|
|
16148
|
+
}
|
|
16149
|
+
.hint code{font-family:var(--mono);font-size:13px;color:var(--text)}
|
|
16150
|
+
.hint code::before{content:"$ ";color:var(--faint)}
|
|
16151
|
+
footer{margin-top:20px;font-size:12.5px;color:var(--faint)}
|
|
16152
|
+
@keyframes rise{from{opacity:0;transform:translateY(8px)}to{opacity:1;transform:none}}
|
|
16153
|
+
@keyframes draw{to{stroke-dashoffset:0}}
|
|
16154
|
+
@media (prefers-reduced-motion:reduce){
|
|
16155
|
+
.card{animation:none}
|
|
16156
|
+
.glyph path{animation:none;stroke-dashoffset:0}
|
|
16157
|
+
}
|
|
16158
|
+
`;
|
|
16159
|
+
var TONES = {
|
|
16160
|
+
success: {
|
|
16161
|
+
color: "#4ade80",
|
|
16162
|
+
soft: "rgba(74,222,128,.12)",
|
|
16163
|
+
edge: "rgba(74,222,128,.3)",
|
|
16164
|
+
glow: "rgba(74,222,128,.09)",
|
|
16165
|
+
icon: CHECK_ICON
|
|
16166
|
+
},
|
|
16167
|
+
failure: {
|
|
16168
|
+
color: "#ff5c5c",
|
|
16169
|
+
soft: "rgba(255,92,92,.12)",
|
|
16170
|
+
edge: "rgba(255,92,92,.3)",
|
|
16171
|
+
glow: "rgba(255,92,92,.09)",
|
|
16172
|
+
icon: CROSS_ICON
|
|
16173
|
+
}
|
|
16174
|
+
};
|
|
16175
|
+
function page(options) {
|
|
16176
|
+
const tone = TONES[options.tone];
|
|
16177
|
+
const title = escapeHtml(options.title);
|
|
16178
|
+
const detail = options.detail ? `<div class="detail">${escapeHtml(options.detail)}</div>` : "";
|
|
16179
|
+
return `<!doctype html>
|
|
16180
|
+
<html lang="en">
|
|
16181
|
+
<head>
|
|
16182
|
+
<meta charset="utf-8">
|
|
16183
|
+
<meta name="viewport" content="width=device-width,initial-scale=1">
|
|
16184
|
+
<title>${title} \xB7 naro</title>
|
|
16185
|
+
<link rel="icon" href="${FAVICON}">
|
|
16186
|
+
<style>:root{--tone:${tone.color};--tone-soft:${tone.soft};--tone-edge:${tone.edge};--glow:${tone.glow}}${STYLES}</style>
|
|
16187
|
+
</head>
|
|
16188
|
+
<body>
|
|
16189
|
+
<main class="card">
|
|
16190
|
+
<span class="brand"><span class="brand-mark">n</span>naro</span>
|
|
16191
|
+
<div class="glyph">${tone.icon}</div>
|
|
16192
|
+
<h1>${title}</h1>
|
|
16193
|
+
<p>${escapeHtml(options.message)}</p>
|
|
16194
|
+
${detail}
|
|
16195
|
+
<div class="hint">
|
|
16196
|
+
<span class="hint-label">${escapeHtml(options.hint.label)}</span>
|
|
16197
|
+
<code>${escapeHtml(options.hint.command)}</code>
|
|
16198
|
+
</div>
|
|
16199
|
+
<footer>You can close this tab.</footer>
|
|
16200
|
+
</main>
|
|
16201
|
+
</body>
|
|
16202
|
+
</html>`;
|
|
16203
|
+
}
|
|
16204
|
+
function successPage() {
|
|
16205
|
+
return page({
|
|
16206
|
+
tone: "success",
|
|
16207
|
+
title: "You're signed in",
|
|
16208
|
+
message: "The CLI saved your session. Head back to the terminal to keep going.",
|
|
16209
|
+
hint: { label: "NEXT", command: "naro deploy" }
|
|
16210
|
+
});
|
|
16211
|
+
}
|
|
16212
|
+
function failurePage(reason) {
|
|
16213
|
+
return page({
|
|
16214
|
+
tone: "failure",
|
|
16215
|
+
title: "Sign-in failed",
|
|
16216
|
+
message: "Nothing was saved. Return to the terminal and try again.",
|
|
16217
|
+
detail: reason,
|
|
16218
|
+
hint: { label: "RETRY", command: "naro login" }
|
|
16219
|
+
});
|
|
16220
|
+
}
|
|
16221
|
+
|
|
16222
|
+
// ../../packages/sdk/src/callback-server.ts
|
|
16223
|
+
function sendPage(res, status, html) {
|
|
16224
|
+
res.statusCode = status;
|
|
16225
|
+
res.setHeader("content-type", "text/html; charset=utf-8");
|
|
16226
|
+
res.setHeader("cache-control", "no-store");
|
|
16227
|
+
res.end(html);
|
|
16228
|
+
}
|
|
15961
16229
|
async function startCallbackServer() {
|
|
15962
16230
|
let resolveCode;
|
|
15963
16231
|
let rejectCode;
|
|
15964
|
-
const codePromise = new Promise((
|
|
15965
|
-
resolveCode =
|
|
16232
|
+
const codePromise = new Promise((resolve5, reject) => {
|
|
16233
|
+
resolveCode = resolve5;
|
|
15966
16234
|
rejectCode = reject;
|
|
15967
16235
|
});
|
|
15968
16236
|
const server = createServer((req, res) => {
|
|
@@ -15975,21 +16243,23 @@ async function startCallbackServer() {
|
|
|
15975
16243
|
const error51 = url2.searchParams.get("error");
|
|
15976
16244
|
const code = url2.searchParams.get("code");
|
|
15977
16245
|
if (error51) {
|
|
15978
|
-
|
|
15979
|
-
res
|
|
16246
|
+
const description = url2.searchParams.get("error_description");
|
|
16247
|
+
sendPage(res, 200, failurePage(description ?? error51));
|
|
15980
16248
|
rejectCode(new Error(`GitHub authorization failed: ${error51}`));
|
|
15981
16249
|
return;
|
|
15982
16250
|
}
|
|
15983
16251
|
if (!code) {
|
|
15984
|
-
|
|
15985
|
-
|
|
16252
|
+
sendPage(
|
|
16253
|
+
res,
|
|
16254
|
+
400,
|
|
16255
|
+
failurePage("The callback carried no authorization code.")
|
|
16256
|
+
);
|
|
15986
16257
|
return;
|
|
15987
16258
|
}
|
|
15988
|
-
res
|
|
15989
|
-
res.end(DONE_HTML);
|
|
16259
|
+
sendPage(res, 200, successPage());
|
|
15990
16260
|
resolveCode(code);
|
|
15991
16261
|
});
|
|
15992
|
-
await new Promise((
|
|
16262
|
+
await new Promise((resolve5) => server.listen(0, "127.0.0.1", resolve5));
|
|
15993
16263
|
const port = server.address().port;
|
|
15994
16264
|
return {
|
|
15995
16265
|
port,
|
|
@@ -16421,7 +16691,7 @@ var DeploymentsApi = class {
|
|
|
16421
16691
|
async wait(deploymentId, options = {}) {
|
|
16422
16692
|
const intervalMs = options.intervalMs ?? 2500;
|
|
16423
16693
|
const timeoutMs = options.timeoutMs ?? 15 * 6e4;
|
|
16424
|
-
const sleep = options.sleep ?? ((ms) => new Promise((
|
|
16694
|
+
const sleep = options.sleep ?? ((ms) => new Promise((resolve5) => setTimeout(resolve5, ms)));
|
|
16425
16695
|
const startedAt = Date.now();
|
|
16426
16696
|
let lastStatus = null;
|
|
16427
16697
|
let lastSequence = -1;
|
|
@@ -16930,10 +17200,10 @@ var SUPPORTED_REMOTE = [
|
|
|
16930
17200
|
/^ssh:\/\/[^\s]+$/,
|
|
16931
17201
|
/^git@[A-Za-z0-9.-]+:[A-Za-z0-9._/-]+(\.git)?$/
|
|
16932
17202
|
];
|
|
16933
|
-
var defaultExec = (args, cwd) => new Promise((
|
|
17203
|
+
var defaultExec = (args, cwd) => new Promise((resolve5, reject) => {
|
|
16934
17204
|
execFile("git", args, { cwd }, (error51, stdout) => {
|
|
16935
17205
|
if (error51) reject(error51);
|
|
16936
|
-
else
|
|
17206
|
+
else resolve5(stdout);
|
|
16937
17207
|
});
|
|
16938
17208
|
});
|
|
16939
17209
|
async function tryExec(exec, args, cwd) {
|
|
@@ -16951,9 +17221,11 @@ async function detectGitInfo(dir, exec = defaultExec) {
|
|
|
16951
17221
|
["rev-parse", "--abbrev-ref", "HEAD"],
|
|
16952
17222
|
dir
|
|
16953
17223
|
);
|
|
17224
|
+
const prefix = await tryExec(exec, ["rev-parse", "--show-prefix"], dir);
|
|
16954
17225
|
return {
|
|
16955
17226
|
repositoryUrl: remote !== null && SUPPORTED_REMOTE.some((re) => re.test(remote)) ? remote : null,
|
|
16956
|
-
branch: branch !== null && branch !== "HEAD" ? branch : null
|
|
17227
|
+
branch: branch !== null && branch !== "HEAD" ? branch : null,
|
|
17228
|
+
prefix: prefix === null ? null : prefix.replace(/\/+$/, "") || null
|
|
16957
17229
|
};
|
|
16958
17230
|
}
|
|
16959
17231
|
|
|
@@ -17032,7 +17304,7 @@ function manifest(artifact) {
|
|
|
17032
17304
|
modules: [artifact.entrypoint, ...artifact.modules].map(meta3),
|
|
17033
17305
|
assets: artifact.assets.map((file2) => ({
|
|
17034
17306
|
...meta3(file2),
|
|
17035
|
-
hash: hashAsset(file2.data)
|
|
17307
|
+
hash: hashAsset(file2.data, file2.contentType)
|
|
17036
17308
|
})),
|
|
17037
17309
|
compatibilityDate: artifact.compatibilityDate,
|
|
17038
17310
|
compatibilityFlags: artifact.compatibilityFlags,
|
|
@@ -17081,7 +17353,7 @@ function eventsObserver(cp2, deploymentId, token, fetchImpl = fetch) {
|
|
|
17081
17353
|
} catch (error51) {
|
|
17082
17354
|
lastError = error51;
|
|
17083
17355
|
await new Promise(
|
|
17084
|
-
(
|
|
17356
|
+
(resolve5) => setTimeout(resolve5, 500 * (attempt + 1))
|
|
17085
17357
|
);
|
|
17086
17358
|
}
|
|
17087
17359
|
}
|
|
@@ -17132,7 +17404,8 @@ async function detectPackageManagerLenient(root) {
|
|
|
17132
17404
|
"--no-audit",
|
|
17133
17405
|
"--no-fund",
|
|
17134
17406
|
"--legacy-peer-deps"
|
|
17135
|
-
]
|
|
17407
|
+
],
|
|
17408
|
+
cacheArgs: (dir) => ["--cache", dir]
|
|
17136
17409
|
};
|
|
17137
17410
|
}
|
|
17138
17411
|
}
|
|
@@ -17172,11 +17445,12 @@ function localDependencies(sourceDir) {
|
|
|
17172
17445
|
async function deployDirectory(sourceDir, options, log2) {
|
|
17173
17446
|
const cpUrl = controlPlaneUrl();
|
|
17174
17447
|
const production = options.production ?? false;
|
|
17175
|
-
const
|
|
17448
|
+
const appRoot = options.rootDirectory ? resolve4(sourceDir, options.rootDirectory) : sourceDir;
|
|
17449
|
+
const framework = detectFramework2(appRoot);
|
|
17176
17450
|
if (!framework.deployable && !options.force) {
|
|
17177
17451
|
throw new NaroError(
|
|
17178
17452
|
"FRAMEWORK_UNSUPPORTED",
|
|
17179
|
-
framework.framework === "unknown" ?
|
|
17453
|
+
framework.framework === "unknown" ? `No supported framework detected in ${appRoot}. Naro deploys Next.js, Vite, Create React App, and static sites \u2014 run this inside one of those projects, or point at it with --root <dir>.` : `${framework.name} projects are not deployable on Naro yet.`,
|
|
17180
17454
|
{ details: { framework: framework.framework } }
|
|
17181
17455
|
);
|
|
17182
17456
|
}
|
|
@@ -17193,9 +17467,10 @@ async function deployDirectory(sourceDir, options, log2) {
|
|
|
17193
17467
|
getAccessToken: () => auth.getAccessToken(),
|
|
17194
17468
|
...options.fetch ? { fetch: options.fetch } : {}
|
|
17195
17469
|
});
|
|
17196
|
-
const name = slugify2(options.name ?? await projectName(
|
|
17197
|
-
const git = await detectGitInfo(sourceDir);
|
|
17470
|
+
const name = slugify2(options.name ?? await projectName(appRoot));
|
|
17471
|
+
const git = await (options.gitInfo ?? detectGitInfo)(sourceDir);
|
|
17198
17472
|
const branch = options.branch ?? git.branch;
|
|
17473
|
+
const repositoryRoot = [git.prefix, options.rootDirectory].filter(Boolean).join("/") || null;
|
|
17199
17474
|
if (options.projectId) log2(`\u25B8 linked project ${options.projectId}`);
|
|
17200
17475
|
else if (git.repositoryUrl) log2(`\u25B8 linking ${git.repositoryUrl}`);
|
|
17201
17476
|
if (options.env && Object.keys(options.env).length > 0)
|
|
@@ -17233,9 +17508,7 @@ async function deployDirectory(sourceDir, options, log2) {
|
|
|
17233
17508
|
production,
|
|
17234
17509
|
...git.repositoryUrl != null && { repositoryUrl: git.repositoryUrl },
|
|
17235
17510
|
...branch != null && { branch },
|
|
17236
|
-
...
|
|
17237
|
-
rootDirectory: options.rootDirectory
|
|
17238
|
-
},
|
|
17511
|
+
...repositoryRoot != null && { rootDirectory: repositoryRoot },
|
|
17239
17512
|
...Object.keys(envToSend).length > 0 && { env: envToSend }
|
|
17240
17513
|
});
|
|
17241
17514
|
if (ctx.envSkipped?.length)
|
|
@@ -17255,10 +17528,16 @@ async function deployDirectory(sourceDir, options, log2) {
|
|
|
17255
17528
|
projectId: ctx.projectId,
|
|
17256
17529
|
repositoryUrl: sourceDir,
|
|
17257
17530
|
branch: ctx.branch,
|
|
17258
|
-
rootDirectory: options.rootDirectory ??
|
|
17531
|
+
rootDirectory: options.rootDirectory ?? null,
|
|
17259
17532
|
env: { ...ctx.env, ...envToSend }
|
|
17260
17533
|
},
|
|
17261
|
-
observer
|
|
17534
|
+
observer,
|
|
17535
|
+
createCacheTransport({
|
|
17536
|
+
deploymentId: ctx.deploymentId,
|
|
17537
|
+
controlPlaneUrl: cpUrl,
|
|
17538
|
+
callbackToken: ctx.callbackToken,
|
|
17539
|
+
...options.fetch ? { fetch: options.fetch } : { fetch }
|
|
17540
|
+
})
|
|
17262
17541
|
);
|
|
17263
17542
|
artifact = result.artifact;
|
|
17264
17543
|
} catch (error51) {
|
package/dist/deploy.d.ts
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
|
+
import { type CacheTransport } from "@next-deploy/build";
|
|
1
2
|
import { type CliGitConnection } from "@next-deploy/sdk";
|
|
2
3
|
import type { BuildObserver, DeploymentArtifact, DeploymentStatus } from "./artifact-types";
|
|
4
|
+
import { type GitInfo } from "./git-info";
|
|
3
5
|
/** The slice of LocalBuildExecutor the deploy flow needs — injectable in tests. */
|
|
4
6
|
export interface DeployBuildExecutor {
|
|
5
7
|
build(input: {
|
|
@@ -9,7 +11,7 @@ export interface DeployBuildExecutor {
|
|
|
9
11
|
branch: string;
|
|
10
12
|
rootDirectory: string | null;
|
|
11
13
|
env: Record<string, string>;
|
|
12
|
-
}, observer: BuildObserver): Promise<{
|
|
14
|
+
}, observer: BuildObserver, cache?: CacheTransport): Promise<{
|
|
13
15
|
artifact: DeploymentArtifact;
|
|
14
16
|
}>;
|
|
15
17
|
}
|
|
@@ -38,6 +40,8 @@ export interface DeployOptions {
|
|
|
38
40
|
fetch?: typeof fetch;
|
|
39
41
|
/** Test seam: replaces the local OpenNext build. */
|
|
40
42
|
executor?: DeployBuildExecutor;
|
|
43
|
+
/** Test seam: replaces the `git` calls that read the repository. */
|
|
44
|
+
gitInfo?: (dir: string) => Promise<GitInfo>;
|
|
41
45
|
/**
|
|
42
46
|
* 소스가 참조하지만 프로젝트에 없는 env 키를 받아, 등록할 값을 돌려준다.
|
|
43
47
|
* 대화형 CLI만 전달한다 — MCP·CI는 생략하고 안내만 받는다.
|
package/dist/deploy.js
CHANGED
package/dist/git-info.d.ts
CHANGED
|
@@ -1,6 +1,16 @@
|
|
|
1
1
|
export interface GitInfo {
|
|
2
2
|
repositoryUrl: string | null;
|
|
3
3
|
branch: string | null;
|
|
4
|
+
/**
|
|
5
|
+
* Path from the repository root to this directory ("apps/web"), or null at
|
|
6
|
+
* the root / outside a repository.
|
|
7
|
+
*
|
|
8
|
+
* A monorepo app deployed from its own directory builds locally from that
|
|
9
|
+
* directory, so nothing here needs it. A git-triggered build clones the
|
|
10
|
+
* whole repository and has to be told where the app lives — this is that
|
|
11
|
+
* answer, recorded without anyone having to know to pass it.
|
|
12
|
+
*/
|
|
13
|
+
prefix: string | null;
|
|
4
14
|
}
|
|
5
15
|
export type GitExec = (args: string[], cwd: string) => Promise<string>;
|
|
6
16
|
/** Reads local git metadata for the deploy: origin remote URL + current branch. */
|
package/dist/index.js
CHANGED
|
@@ -26,7 +26,7 @@ import {
|
|
|
26
26
|
slugify,
|
|
27
27
|
toNaroError,
|
|
28
28
|
writeProjectLink
|
|
29
|
-
} from "./chunk-
|
|
29
|
+
} from "./chunk-WKJELBOU.js";
|
|
30
30
|
|
|
31
31
|
// src/args.ts
|
|
32
32
|
var BOOLEAN_FLAGS = {
|
|
@@ -1364,6 +1364,7 @@ Usage:
|
|
|
1364
1364
|
naro project inspect Show the linked project
|
|
1365
1365
|
|
|
1366
1366
|
naro deploy [--prod] Build locally and deploy (preview by default)
|
|
1367
|
+
[--root <dir>] [--name <name>] --root points at the app in a monorepo
|
|
1367
1368
|
naro deployments List deployments of the linked project
|
|
1368
1369
|
naro inspect <deployment-id> Show one deployment + build logs
|
|
1369
1370
|
|
|
@@ -1388,6 +1389,8 @@ Global options:
|
|
|
1388
1389
|
--yes, -y Never prompt; assume yes (CI / agents)
|
|
1389
1390
|
--token <t> Access token (or set NARO_TOKEN)
|
|
1390
1391
|
--project <id> Target project (overrides .naro/project.json)
|
|
1392
|
+
--root <dir> App directory inside the repo (monorepos), e.g. apps/web
|
|
1393
|
+
--name <name> Project name for a first deploy (defaults to package.json)
|
|
1391
1394
|
--team <slug> Team scope for project listing/linking
|
|
1392
1395
|
--cwd <dir> Run as if started in <dir>
|
|
1393
1396
|
--prod Deploy/target production
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "naro-cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"bin": {
|
|
6
6
|
"naro": "./dist/index.js"
|
|
@@ -28,10 +28,10 @@
|
|
|
28
28
|
"tsx": "4.23.12",
|
|
29
29
|
"typescript": "7.0.2",
|
|
30
30
|
"vitest": "3.2.7",
|
|
31
|
-
"@next-deploy/core": "0.1.0",
|
|
32
31
|
"@next-deploy/build": "0.1.0",
|
|
33
|
-
"@next-deploy/
|
|
34
|
-
"@next-deploy/sdk": "0.1.0"
|
|
32
|
+
"@next-deploy/core": "0.1.0",
|
|
33
|
+
"@next-deploy/sdk": "0.1.0",
|
|
34
|
+
"@next-deploy/config": "0.1.0"
|
|
35
35
|
},
|
|
36
36
|
"scripts": {
|
|
37
37
|
"build": "tsup && pnpm build:types",
|