naro-cli 0.2.1 → 0.4.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 +120 -4
- package/dist/artifact-types.d.ts +3 -0
- package/dist/{chunk-IC4R5EYE.js → chunk-YLFLDW2H.js} +1602 -399
- package/dist/deploy.d.ts +46 -26
- package/dist/deploy.js +3 -9
- package/dist/env-consent.d.ts +18 -0
- package/dist/index.js +1401 -65
- package/dist/output.d.ts +29 -0
- package/dist/prompt.d.ts +17 -0
- package/package.json +13 -12
- package/dist/callback-server.d.ts +0 -10
- package/dist/index.d.ts +0 -7
- package/dist/login.d.ts +0 -10
- package/dist/session.d.ts +0 -16
- package/dist/supabase-auth.d.ts +0 -11
|
@@ -6,12 +6,63 @@ var __export = (target, all) => {
|
|
|
6
6
|
};
|
|
7
7
|
|
|
8
8
|
// src/deploy.ts
|
|
9
|
-
import { existsSync
|
|
10
|
-
import { cp, mkdtemp as mkdtemp2, readFile as
|
|
11
|
-
import {
|
|
12
|
-
import { basename, dirname, join as
|
|
9
|
+
import { existsSync as existsSync6 } from "fs";
|
|
10
|
+
import { cp, mkdtemp as mkdtemp2, readFile as readFile7, rm as rm2 } from "fs/promises";
|
|
11
|
+
import { tmpdir as tmpdir2 } from "os";
|
|
12
|
+
import { basename, dirname, join as join15 } from "path";
|
|
13
13
|
import { fileURLToPath } from "url";
|
|
14
14
|
|
|
15
|
+
// ../../packages/build/src/artifact-upload.ts
|
|
16
|
+
function artifactUploadEntries(artifact) {
|
|
17
|
+
return [
|
|
18
|
+
...[artifact.entrypoint, ...artifact.modules].map(
|
|
19
|
+
(file2) => ({ kind: "module", file: file2 })
|
|
20
|
+
),
|
|
21
|
+
...artifact.assets.map((file2) => ({
|
|
22
|
+
kind: "asset",
|
|
23
|
+
file: file2
|
|
24
|
+
}))
|
|
25
|
+
];
|
|
26
|
+
}
|
|
27
|
+
function artifactUrlKey(kind, name) {
|
|
28
|
+
return `${kind}/${name}`;
|
|
29
|
+
}
|
|
30
|
+
var ARTIFACT_URL_BATCH_SIZE = 100;
|
|
31
|
+
function parseArtifactPutUrls(payload) {
|
|
32
|
+
if (typeof payload !== "object" || payload === null) return null;
|
|
33
|
+
const { urls, disabled } = payload;
|
|
34
|
+
if (disabled === true) return null;
|
|
35
|
+
if (typeof urls !== "object" || urls === null) return null;
|
|
36
|
+
const resolved = /* @__PURE__ */ new Map();
|
|
37
|
+
for (const [key, value] of Object.entries(urls)) {
|
|
38
|
+
if (typeof value === "string") resolved.set(key, value);
|
|
39
|
+
}
|
|
40
|
+
return resolved;
|
|
41
|
+
}
|
|
42
|
+
async function uploadArtifactFiles(artifact, ports) {
|
|
43
|
+
const entries = artifactUploadEntries(artifact);
|
|
44
|
+
let proxied = 0;
|
|
45
|
+
for (let index = 0; index < entries.length; index += ARTIFACT_URL_BATCH_SIZE) {
|
|
46
|
+
const batch = entries.slice(index, index + ARTIFACT_URL_BATCH_SIZE);
|
|
47
|
+
const urls = await ports.resolvePutUrls(batch);
|
|
48
|
+
for (const entry of batch) {
|
|
49
|
+
const url2 = urls?.get(artifactUrlKey(entry.kind, entry.file.name));
|
|
50
|
+
if (!url2) {
|
|
51
|
+
proxied += 1;
|
|
52
|
+
await ports.putProxied(entry);
|
|
53
|
+
continue;
|
|
54
|
+
}
|
|
55
|
+
try {
|
|
56
|
+
await ports.putSigned(url2, entry);
|
|
57
|
+
} catch {
|
|
58
|
+
proxied += 1;
|
|
59
|
+
await ports.putProxied(entry);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
return { proxied, total: entries.length };
|
|
64
|
+
}
|
|
65
|
+
|
|
15
66
|
// ../../packages/build/src/cache.ts
|
|
16
67
|
import { createHash } from "crypto";
|
|
17
68
|
import { mkdir, readFile, stat, writeFile } from "fs/promises";
|
|
@@ -116,7 +167,7 @@ function hashAsset(data) {
|
|
|
116
167
|
// ../../packages/build/src/local-build-executor.ts
|
|
117
168
|
import { mkdtemp, rm } from "fs/promises";
|
|
118
169
|
import { tmpdir } from "os";
|
|
119
|
-
import { join as
|
|
170
|
+
import { join as join8 } from "path";
|
|
120
171
|
|
|
121
172
|
// ../../packages/core/src/artifact-manifest.ts
|
|
122
173
|
var MAX_FILE_BYTES = 25 * 1024 * 1024;
|
|
@@ -135,6 +186,27 @@ var DeploymentError = class extends Error {
|
|
|
135
186
|
}
|
|
136
187
|
};
|
|
137
188
|
|
|
189
|
+
// ../../packages/core/src/git-remote.ts
|
|
190
|
+
var SCP_LIKE = /^[^@\s/]+@([^:\s/]+):(.+)$/;
|
|
191
|
+
function parseGitHubRemote(url2) {
|
|
192
|
+
const trimmed = url2.trim();
|
|
193
|
+
if (trimmed === "") return null;
|
|
194
|
+
const scp = SCP_LIKE.exec(trimmed);
|
|
195
|
+
const path = scp ? scp[2] ?? null : pathnameOf(trimmed);
|
|
196
|
+
if (path === null) return null;
|
|
197
|
+
const segments = path.replace(/\.git$/, "").split("/").filter((segment) => segment !== "");
|
|
198
|
+
if (segments.length !== 2) return null;
|
|
199
|
+
const [owner, repo] = segments;
|
|
200
|
+
return { owner, repo, fullName: `${owner}/${repo}` };
|
|
201
|
+
}
|
|
202
|
+
function pathnameOf(url2) {
|
|
203
|
+
try {
|
|
204
|
+
return new URL(url2).pathname;
|
|
205
|
+
} catch {
|
|
206
|
+
return null;
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
138
210
|
// ../../node_modules/.pnpm/zod@4.4.3/node_modules/zod/v4/classic/external.js
|
|
139
211
|
var external_exports = {};
|
|
140
212
|
__export(external_exports, {
|
|
@@ -14843,6 +14915,16 @@ async function pathExists(path) {
|
|
|
14843
14915
|
return false;
|
|
14844
14916
|
}
|
|
14845
14917
|
}
|
|
14918
|
+
async function createPackageManagerConfig(appRoot, writer = async (path, contents) => {
|
|
14919
|
+
await writeFile3(path, contents, "utf8");
|
|
14920
|
+
}) {
|
|
14921
|
+
const packageManagerConfigPath = join4(appRoot, ".next-deploy-user.npmrc");
|
|
14922
|
+
await writer(
|
|
14923
|
+
packageManagerConfigPath,
|
|
14924
|
+
"# Host package-manager credentials are intentionally unavailable.\n"
|
|
14925
|
+
);
|
|
14926
|
+
return packageManagerConfigPath;
|
|
14927
|
+
}
|
|
14846
14928
|
async function createPlatformBuildConfig(appRoot, deploymentId, writer = async (path, contents) => {
|
|
14847
14929
|
await writeFile3(path, contents, "utf8");
|
|
14848
14930
|
}, cloudflareModulePath = createRequire(import.meta.url).resolve(
|
|
@@ -14850,7 +14932,6 @@ async function createPlatformBuildConfig(appRoot, deploymentId, writer = async (
|
|
|
14850
14932
|
), exists3 = pathExists) {
|
|
14851
14933
|
const openNextConfigPath = join4(appRoot, ".next-deploy-open-next.config.mjs");
|
|
14852
14934
|
const wranglerConfigPath = join4(appRoot, ".next-deploy-wrangler.json");
|
|
14853
|
-
const packageManagerConfigPath = join4(appRoot, ".next-deploy-user.npmrc");
|
|
14854
14935
|
const safeDeploymentId = deploymentId.replace(/[^a-z0-9-]/gi, "-");
|
|
14855
14936
|
let hasNextConfig = false;
|
|
14856
14937
|
for (const name of NEXT_CONFIG_FILES) {
|
|
@@ -14890,9 +14971,9 @@ export default defineCloudflareConfig({});
|
|
|
14890
14971
|
)}
|
|
14891
14972
|
`
|
|
14892
14973
|
);
|
|
14893
|
-
await
|
|
14894
|
-
|
|
14895
|
-
|
|
14974
|
+
const packageManagerConfigPath = await createPackageManagerConfig(
|
|
14975
|
+
appRoot,
|
|
14976
|
+
writer
|
|
14896
14977
|
);
|
|
14897
14978
|
return {
|
|
14898
14979
|
openNextConfigPath,
|
|
@@ -14901,9 +14982,129 @@ export default defineCloudflareConfig({});
|
|
|
14901
14982
|
};
|
|
14902
14983
|
}
|
|
14903
14984
|
|
|
14985
|
+
// ../../packages/build/src/frameworks.ts
|
|
14986
|
+
import { readFile as readFile4 } from "fs/promises";
|
|
14987
|
+
import { join as join5 } from "path";
|
|
14988
|
+
var FRAMEWORK_PRESETS = {
|
|
14989
|
+
next: {
|
|
14990
|
+
id: "next",
|
|
14991
|
+
detectDependency: "next",
|
|
14992
|
+
defaultBuildScript: "build",
|
|
14993
|
+
defaultOutputDirectory: ".open-next",
|
|
14994
|
+
needsServerRuntime: true,
|
|
14995
|
+
spaFallback: false,
|
|
14996
|
+
cachePath: ".next/cache"
|
|
14997
|
+
},
|
|
14998
|
+
vite: {
|
|
14999
|
+
id: "vite",
|
|
15000
|
+
detectDependency: "vite",
|
|
15001
|
+
defaultBuildScript: "build",
|
|
15002
|
+
defaultOutputDirectory: "dist",
|
|
15003
|
+
needsServerRuntime: false,
|
|
15004
|
+
spaFallback: true,
|
|
15005
|
+
cachePath: "node_modules/.vite"
|
|
15006
|
+
},
|
|
15007
|
+
cra: {
|
|
15008
|
+
id: "cra",
|
|
15009
|
+
detectDependency: "react-scripts",
|
|
15010
|
+
defaultBuildScript: "build",
|
|
15011
|
+
defaultOutputDirectory: "build",
|
|
15012
|
+
needsServerRuntime: false,
|
|
15013
|
+
spaFallback: true,
|
|
15014
|
+
cachePath: "node_modules/.cache"
|
|
15015
|
+
},
|
|
15016
|
+
static: {
|
|
15017
|
+
id: "static",
|
|
15018
|
+
detectDependency: null,
|
|
15019
|
+
defaultBuildScript: null,
|
|
15020
|
+
defaultOutputDirectory: ".",
|
|
15021
|
+
needsServerRuntime: false,
|
|
15022
|
+
spaFallback: false,
|
|
15023
|
+
cachePath: null
|
|
15024
|
+
}
|
|
15025
|
+
};
|
|
15026
|
+
var DETECTION_ORDER = ["next", "vite", "cra"];
|
|
15027
|
+
var FRAMEWORK_LABELS = {
|
|
15028
|
+
next: "Next.js",
|
|
15029
|
+
vite: "Vite",
|
|
15030
|
+
cra: "Create React App",
|
|
15031
|
+
static: "static files"
|
|
15032
|
+
};
|
|
15033
|
+
function dependencyVersion(packageJson, name) {
|
|
15034
|
+
const version2 = packageJson.dependencies?.[name] ?? packageJson.devDependencies?.[name];
|
|
15035
|
+
return typeof version2 === "string" && version2.trim() !== "" ? version2 : null;
|
|
15036
|
+
}
|
|
15037
|
+
async function readPackageJson(root) {
|
|
15038
|
+
let source;
|
|
15039
|
+
try {
|
|
15040
|
+
source = await readFile4(join5(root, "package.json"), "utf8");
|
|
15041
|
+
} catch (error51) {
|
|
15042
|
+
if (error51.code === "ENOENT") return null;
|
|
15043
|
+
throw new DeploymentError(
|
|
15044
|
+
"FRAMEWORK_DETECTION_FAILED",
|
|
15045
|
+
"Could not read package.json.",
|
|
15046
|
+
{ cause: error51 }
|
|
15047
|
+
);
|
|
15048
|
+
}
|
|
15049
|
+
try {
|
|
15050
|
+
return JSON.parse(source);
|
|
15051
|
+
} catch (error51) {
|
|
15052
|
+
throw new DeploymentError(
|
|
15053
|
+
"FRAMEWORK_DETECTION_FAILED",
|
|
15054
|
+
"The selected root does not contain a valid package.json.",
|
|
15055
|
+
{ cause: error51 }
|
|
15056
|
+
);
|
|
15057
|
+
}
|
|
15058
|
+
}
|
|
15059
|
+
async function detectFramework(root, overrides = {}) {
|
|
15060
|
+
const packageJson = await readPackageJson(root);
|
|
15061
|
+
let preset;
|
|
15062
|
+
if (overrides.framework) {
|
|
15063
|
+
const candidate = FRAMEWORK_PRESETS[overrides.framework] ?? null;
|
|
15064
|
+
if (!candidate || candidate.id !== overrides.framework)
|
|
15065
|
+
throw new DeploymentError(
|
|
15066
|
+
"FRAMEWORK_DETECTION_FAILED",
|
|
15067
|
+
`Unknown framework "${overrides.framework}".`
|
|
15068
|
+
);
|
|
15069
|
+
preset = candidate;
|
|
15070
|
+
} else if (!packageJson) {
|
|
15071
|
+
preset = FRAMEWORK_PRESETS.static;
|
|
15072
|
+
} else {
|
|
15073
|
+
const detected = DETECTION_ORDER.map((id) => FRAMEWORK_PRESETS[id]).find(
|
|
15074
|
+
(candidate) => candidate.detectDependency !== null && dependencyVersion(packageJson, candidate.detectDependency) !== null
|
|
15075
|
+
);
|
|
15076
|
+
if (!detected)
|
|
15077
|
+
throw new DeploymentError(
|
|
15078
|
+
"FRAMEWORK_DETECTION_FAILED",
|
|
15079
|
+
"Could not detect the project framework from package.json. Set a framework (and build command / output directory) in the project settings."
|
|
15080
|
+
);
|
|
15081
|
+
preset = detected;
|
|
15082
|
+
}
|
|
15083
|
+
const buildScript = overrides.buildCommand ?? preset.defaultBuildScript;
|
|
15084
|
+
if (buildScript !== null) {
|
|
15085
|
+
if (!packageJson)
|
|
15086
|
+
throw new DeploymentError(
|
|
15087
|
+
"FRAMEWORK_DETECTION_FAILED",
|
|
15088
|
+
"A build command requires a package.json."
|
|
15089
|
+
);
|
|
15090
|
+
if (typeof packageJson.scripts?.[buildScript] !== "string")
|
|
15091
|
+
throw new DeploymentError(
|
|
15092
|
+
"BUILD_FAILED",
|
|
15093
|
+
`The project must declare a "${buildScript}" script.`
|
|
15094
|
+
);
|
|
15095
|
+
}
|
|
15096
|
+
const version2 = packageJson && preset.detectDependency ? dependencyVersion(packageJson, preset.detectDependency) : null;
|
|
15097
|
+
return {
|
|
15098
|
+
preset,
|
|
15099
|
+
buildScript,
|
|
15100
|
+
outputDirectory: overrides.outputDirectory ?? preset.defaultOutputDirectory,
|
|
15101
|
+
description: version2 ? `${FRAMEWORK_LABELS[preset.id]} ${version2}` : FRAMEWORK_LABELS[preset.id]
|
|
15102
|
+
};
|
|
15103
|
+
}
|
|
15104
|
+
|
|
14904
15105
|
// ../../packages/build/src/open-next.ts
|
|
14905
|
-
import { access as access4, readdir, readFile as
|
|
14906
|
-
import { extname, isAbsolute, join as
|
|
15106
|
+
import { access as access4, readdir, readFile as readFile5 } from "fs/promises";
|
|
15107
|
+
import { extname, isAbsolute, join as join6, relative, resolve, sep } from "path";
|
|
14907
15108
|
function parseJsonc(source) {
|
|
14908
15109
|
let output = "";
|
|
14909
15110
|
let index = 0;
|
|
@@ -14998,7 +15199,7 @@ async function listFiles(root) {
|
|
|
14998
15199
|
for (const entry of entries.sort(
|
|
14999
15200
|
(left, right) => left.name.localeCompare(right.name)
|
|
15000
15201
|
)) {
|
|
15001
|
-
const path =
|
|
15202
|
+
const path = join6(directory, entry.name);
|
|
15002
15203
|
if (entry.isSymbolicLink()) {
|
|
15003
15204
|
throw new DeploymentError(
|
|
15004
15205
|
"ARTIFACT_INVALID",
|
|
@@ -15009,7 +15210,7 @@ async function listFiles(root) {
|
|
|
15009
15210
|
else if (entry.isFile()) {
|
|
15010
15211
|
files.push({
|
|
15011
15212
|
name: relative(root, path).split(sep).join("/"),
|
|
15012
|
-
data: await
|
|
15213
|
+
data: await readFile5(path),
|
|
15013
15214
|
contentType: contentType(path)
|
|
15014
15215
|
});
|
|
15015
15216
|
}
|
|
@@ -15032,7 +15233,7 @@ async function findWranglerConfig(root, configuredPath) {
|
|
|
15032
15233
|
return resolved;
|
|
15033
15234
|
}
|
|
15034
15235
|
for (const name of ["wrangler.jsonc", "wrangler.json"]) {
|
|
15035
|
-
const path =
|
|
15236
|
+
const path = join6(root, name);
|
|
15036
15237
|
try {
|
|
15037
15238
|
await access4(path);
|
|
15038
15239
|
return path;
|
|
@@ -15048,7 +15249,7 @@ async function readOpenNextArtifact(appRoot, identity, wranglerConfigPath) {
|
|
|
15048
15249
|
try {
|
|
15049
15250
|
const configPath = await findWranglerConfig(appRoot, wranglerConfigPath);
|
|
15050
15251
|
const metadata = parseJsonc(
|
|
15051
|
-
await
|
|
15252
|
+
await readFile5(configPath, "utf8")
|
|
15052
15253
|
);
|
|
15053
15254
|
if (typeof metadata.main !== "string" || typeof metadata.compatibility_date !== "string") {
|
|
15054
15255
|
throw new DeploymentError(
|
|
@@ -15070,7 +15271,7 @@ async function readOpenNextArtifact(appRoot, identity, wranglerConfigPath) {
|
|
|
15070
15271
|
);
|
|
15071
15272
|
}
|
|
15072
15273
|
await access4(entrypointPath);
|
|
15073
|
-
const configuredAssets = typeof metadata.assets?.directory === "string" ? confinedPath(appRoot, metadata.assets.directory, "Assets directory") :
|
|
15274
|
+
const configuredAssets = typeof metadata.assets?.directory === "string" ? confinedPath(appRoot, metadata.assets.directory, "Assets directory") : join6(outputRoot, "assets");
|
|
15074
15275
|
const assetsRelation = relative(outputRoot, configuredAssets);
|
|
15075
15276
|
if (assetsRelation === ".." || assetsRelation.startsWith(`..${sep}`) || isAbsolute(assetsRelation)) {
|
|
15076
15277
|
throw new DeploymentError(
|
|
@@ -15078,7 +15279,7 @@ async function readOpenNextArtifact(appRoot, identity, wranglerConfigPath) {
|
|
|
15078
15279
|
"Assets directory must be inside .open-next."
|
|
15079
15280
|
);
|
|
15080
15281
|
}
|
|
15081
|
-
const bundleRoot =
|
|
15282
|
+
const bundleRoot = join6(outputRoot, "worker-bundle");
|
|
15082
15283
|
let bundleFiles = null;
|
|
15083
15284
|
try {
|
|
15084
15285
|
await access4(bundleRoot);
|
|
@@ -15263,48 +15464,99 @@ function resolveAppRoot(checkout, configuredRoot) {
|
|
|
15263
15464
|
return appRoot;
|
|
15264
15465
|
}
|
|
15265
15466
|
|
|
15266
|
-
// ../../packages/build/src/
|
|
15267
|
-
import { readFile as
|
|
15268
|
-
import { join as
|
|
15269
|
-
|
|
15270
|
-
|
|
15271
|
-
|
|
15272
|
-
|
|
15273
|
-
|
|
15467
|
+
// ../../packages/build/src/static-artifact.ts
|
|
15468
|
+
import { readdir as readdir2, readFile as readFile6 } from "fs/promises";
|
|
15469
|
+
import { isAbsolute as isAbsolute3, join as join7, relative as relative3, resolve as resolve3, sep as sep2 } from "path";
|
|
15470
|
+
var SHIM_WORKER_SOURCE = "export default { fetch: (request, env) => env.ASSETS.fetch(request) };\n";
|
|
15471
|
+
var EXCLUDED_DIRECTORIES = /* @__PURE__ */ new Set(["node_modules"]);
|
|
15472
|
+
var ALLOWED_DOTFILE = ".well-known";
|
|
15473
|
+
function excluded(name) {
|
|
15474
|
+
if (name === ALLOWED_DOTFILE) return false;
|
|
15475
|
+
return name.startsWith(".") || EXCLUDED_DIRECTORIES.has(name);
|
|
15476
|
+
}
|
|
15477
|
+
async function readStaticArtifact(appRoot, outputDirectory, identity, options, onWarn) {
|
|
15478
|
+
if (isAbsolute3(outputDirectory))
|
|
15479
|
+
throw new DeploymentError(
|
|
15480
|
+
"ARTIFACT_INVALID",
|
|
15481
|
+
"Output directory must be relative to the application root."
|
|
15274
15482
|
);
|
|
15275
|
-
|
|
15483
|
+
const outputRoot = resolve3(appRoot, outputDirectory);
|
|
15484
|
+
const relation = relative3(appRoot, outputRoot);
|
|
15485
|
+
if (relation === ".." || relation.startsWith(`..${sep2}`) || isAbsolute3(relation))
|
|
15276
15486
|
throw new DeploymentError(
|
|
15277
|
-
"
|
|
15278
|
-
"
|
|
15279
|
-
{
|
|
15280
|
-
cause: error51
|
|
15281
|
-
}
|
|
15487
|
+
"ARTIFACT_INVALID",
|
|
15488
|
+
"Output directory escapes the application root."
|
|
15282
15489
|
);
|
|
15490
|
+
const assets = [];
|
|
15491
|
+
async function visit(directory) {
|
|
15492
|
+
const entries = await readdir2(directory, { withFileTypes: true });
|
|
15493
|
+
for (const entry of entries.sort(
|
|
15494
|
+
(left, right) => left.name.localeCompare(right.name)
|
|
15495
|
+
)) {
|
|
15496
|
+
if (excluded(entry.name)) continue;
|
|
15497
|
+
const path = join7(directory, entry.name);
|
|
15498
|
+
if (entry.isSymbolicLink())
|
|
15499
|
+
throw new DeploymentError(
|
|
15500
|
+
"ARTIFACT_INVALID",
|
|
15501
|
+
"Static output cannot contain symbolic links."
|
|
15502
|
+
);
|
|
15503
|
+
if (entry.isDirectory()) await visit(path);
|
|
15504
|
+
else if (entry.isFile()) {
|
|
15505
|
+
assets.push({
|
|
15506
|
+
name: relative3(outputRoot, path).split(sep2).join("/"),
|
|
15507
|
+
data: await readFile6(path),
|
|
15508
|
+
contentType: contentType(path)
|
|
15509
|
+
});
|
|
15510
|
+
}
|
|
15511
|
+
}
|
|
15283
15512
|
}
|
|
15284
|
-
|
|
15285
|
-
|
|
15513
|
+
try {
|
|
15514
|
+
await visit(outputRoot);
|
|
15515
|
+
} catch (error51) {
|
|
15516
|
+
if (error51 instanceof DeploymentError) throw error51;
|
|
15286
15517
|
throw new DeploymentError(
|
|
15287
|
-
"
|
|
15288
|
-
|
|
15518
|
+
"ARTIFACT_INVALID",
|
|
15519
|
+
`Could not read the build output directory "${outputDirectory}".`,
|
|
15520
|
+
{ cause: error51 }
|
|
15289
15521
|
);
|
|
15290
15522
|
}
|
|
15291
|
-
if (
|
|
15523
|
+
if (assets.length === 0)
|
|
15292
15524
|
throw new DeploymentError(
|
|
15293
|
-
"
|
|
15294
|
-
|
|
15525
|
+
"ARTIFACT_INVALID",
|
|
15526
|
+
`The build output directory "${outputDirectory}" contains no files.`
|
|
15295
15527
|
);
|
|
15296
|
-
|
|
15297
|
-
|
|
15528
|
+
if (!assets.some((file2) => file2.name === "index.html"))
|
|
15529
|
+
await onWarn?.(
|
|
15530
|
+
`No index.html in "${outputDirectory}" \u2014 the site root will not serve a page.`
|
|
15531
|
+
);
|
|
15532
|
+
return {
|
|
15533
|
+
...identity,
|
|
15534
|
+
entrypoint: {
|
|
15535
|
+
name: "worker.js",
|
|
15536
|
+
data: new TextEncoder().encode(SHIM_WORKER_SOURCE),
|
|
15537
|
+
contentType: "application/javascript"
|
|
15538
|
+
},
|
|
15539
|
+
modules: [],
|
|
15540
|
+
assets,
|
|
15541
|
+
compatibilityDate: COMPATIBILITY_DATE,
|
|
15542
|
+
compatibilityFlags: [],
|
|
15543
|
+
assetsBinding: "ASSETS",
|
|
15544
|
+
assetsConfig: {
|
|
15545
|
+
notFoundHandling: options.spaFallback ? "single-page-application" : "404-page"
|
|
15546
|
+
}
|
|
15547
|
+
};
|
|
15298
15548
|
}
|
|
15299
15549
|
|
|
15300
15550
|
// ../../packages/build/src/local-build-executor.ts
|
|
15301
15551
|
var defaultDependencies = {
|
|
15302
|
-
createWorkspace: () => mkdtemp(
|
|
15552
|
+
createWorkspace: () => mkdtemp(join8(tmpdir(), "next-deploy-build-")),
|
|
15303
15553
|
removeWorkspace: (path) => rm(path, { recursive: true, force: true }),
|
|
15304
15554
|
runCommand,
|
|
15305
15555
|
detectPackageManager,
|
|
15306
15556
|
resolveAppRoot,
|
|
15307
|
-
|
|
15557
|
+
detectFramework,
|
|
15558
|
+
readStaticArtifact,
|
|
15559
|
+
createPackageManagerConfig,
|
|
15308
15560
|
createPlatformBuildConfig,
|
|
15309
15561
|
readOpenNextArtifact,
|
|
15310
15562
|
openNextCommand: "opennextjs-cloudflare",
|
|
@@ -15350,14 +15602,14 @@ var LocalBuildExecutor = class {
|
|
|
15350
15602
|
}
|
|
15351
15603
|
async build(input, observer, cache) {
|
|
15352
15604
|
const workspace = await this.dependencies.createWorkspace();
|
|
15353
|
-
const checkout =
|
|
15354
|
-
const pmCacheDir =
|
|
15605
|
+
const checkout = join8(workspace, "checkout");
|
|
15606
|
+
const pmCacheDir = join8(workspace, ".pm-store");
|
|
15355
15607
|
let cacheSeq = 0;
|
|
15356
15608
|
const cachePorts = cache ? {
|
|
15357
15609
|
transport: cache,
|
|
15358
15610
|
fetch: this.dependencies.cacheFetch ?? ((url2, init) => fetch(url2, init)),
|
|
15359
15611
|
runCommand: this.dependencies.runCommand,
|
|
15360
|
-
tmpFile: (suffix) =>
|
|
15612
|
+
tmpFile: (suffix) => join8(workspace, `cache-${String(cacheSeq++)}${suffix}`)
|
|
15361
15613
|
} : null;
|
|
15362
15614
|
try {
|
|
15363
15615
|
await notifyStatus(observer, "CLONING");
|
|
@@ -15420,141 +15672,174 @@ var LocalBuildExecutor = class {
|
|
|
15420
15672
|
checkout,
|
|
15421
15673
|
input.rootDirectory
|
|
15422
15674
|
);
|
|
15423
|
-
const
|
|
15424
|
-
|
|
15425
|
-
|
|
15426
|
-
|
|
15427
|
-
|
|
15428
|
-
);
|
|
15429
|
-
const
|
|
15430
|
-
|
|
15431
|
-
|
|
15432
|
-
|
|
15433
|
-
|
|
15434
|
-
|
|
15435
|
-
|
|
15436
|
-
|
|
15437
|
-
|
|
15438
|
-
|
|
15439
|
-
|
|
15440
|
-
|
|
15441
|
-
|
|
15442
|
-
|
|
15443
|
-
|
|
15444
|
-
if (!installCommand)
|
|
15445
|
-
throw new DeploymentError(
|
|
15446
|
-
"UNSUPPORTED_PACKAGE_MANAGER",
|
|
15447
|
-
"Install command is empty."
|
|
15675
|
+
const resolved = await this.dependencies.detectFramework(appRoot, {
|
|
15676
|
+
framework: input.framework ?? null,
|
|
15677
|
+
buildCommand: input.buildCommand ?? null,
|
|
15678
|
+
outputDirectory: input.outputDirectory ?? null
|
|
15679
|
+
});
|
|
15680
|
+
await info(observer, "CLONING", `Detected ${resolved.description}`);
|
|
15681
|
+
const isNext = resolved.preset.id === "next";
|
|
15682
|
+
let platformConfig = null;
|
|
15683
|
+
let packageManagerConfigPath = null;
|
|
15684
|
+
if (isNext) {
|
|
15685
|
+
const conversion = await (this.dependencies.convertProxyToMiddleware ?? convertProxyToMiddleware)(appRoot);
|
|
15686
|
+
if (conversion.converted) {
|
|
15687
|
+
await observer.onLog({
|
|
15688
|
+
level: "WARN",
|
|
15689
|
+
stage: "CLONING",
|
|
15690
|
+
message: `Converted ${conversion.file ?? "proxy"} to edge middleware. Next.js 16 proxy runs on the Node.js runtime, which Cloudflare does not support; middleware runs on the Edge runtime, so Node.js-only APIs are unavailable.`
|
|
15691
|
+
});
|
|
15692
|
+
}
|
|
15693
|
+
platformConfig = await this.dependencies.createPlatformBuildConfig(
|
|
15694
|
+
appRoot,
|
|
15695
|
+
input.deploymentId
|
|
15448
15696
|
);
|
|
15449
|
-
|
|
15450
|
-
|
|
15451
|
-
|
|
15452
|
-
|
|
15453
|
-
|
|
15454
|
-
|
|
15455
|
-
|
|
15456
|
-
|
|
15457
|
-
|
|
15697
|
+
packageManagerConfigPath = platformConfig.packageManagerConfigPath;
|
|
15698
|
+
} else if (resolved.buildScript !== null) {
|
|
15699
|
+
packageManagerConfigPath = await this.dependencies.createPackageManagerConfig(appRoot);
|
|
15700
|
+
}
|
|
15701
|
+
if (resolved.buildScript !== null) {
|
|
15702
|
+
await notifyStatus(observer, "INSTALLING");
|
|
15703
|
+
const packageManager = await this.dependencies.detectPackageManager(checkout);
|
|
15704
|
+
const [installCommand, ...installArgs] = packageManager.install;
|
|
15705
|
+
if (!installCommand)
|
|
15706
|
+
throw new DeploymentError(
|
|
15707
|
+
"UNSUPPORTED_PACKAGE_MANAGER",
|
|
15708
|
+
"Install command is empty."
|
|
15709
|
+
);
|
|
15710
|
+
let depsKey = null;
|
|
15711
|
+
let depsRestored = false;
|
|
15712
|
+
if (cachePorts) {
|
|
15713
|
+
depsKey = await computeDepsCacheKey(checkout, packageManager.name);
|
|
15714
|
+
if (depsKey) {
|
|
15715
|
+
depsRestored = await tryRestore(
|
|
15716
|
+
"deps",
|
|
15717
|
+
depsKey,
|
|
15718
|
+
pmCacheDir,
|
|
15719
|
+
cachePorts,
|
|
15720
|
+
observer
|
|
15721
|
+
);
|
|
15722
|
+
}
|
|
15723
|
+
}
|
|
15724
|
+
await this.dependencies.runCommand(
|
|
15725
|
+
{
|
|
15726
|
+
command: installCommand,
|
|
15727
|
+
args: installArgs,
|
|
15728
|
+
cwd: checkout,
|
|
15729
|
+
env: {
|
|
15730
|
+
NODE_ENV: "development",
|
|
15731
|
+
NPM_CONFIG_USERCONFIG: packageManagerConfigPath,
|
|
15732
|
+
...cachePorts ? {
|
|
15733
|
+
npm_config_store_dir: pmCacheDir,
|
|
15734
|
+
npm_config_cache: pmCacheDir,
|
|
15735
|
+
YARN_CACHE_FOLDER: pmCacheDir
|
|
15736
|
+
} : {}
|
|
15737
|
+
},
|
|
15738
|
+
timeoutMs: 10 * 6e4,
|
|
15739
|
+
stage: "INSTALLING",
|
|
15740
|
+
errorCode: "INSTALL_FAILED"
|
|
15741
|
+
},
|
|
15742
|
+
observer
|
|
15743
|
+
);
|
|
15744
|
+
await notifyStatus(observer, "BUILDING");
|
|
15745
|
+
const buildCacheDir = resolved.preset.cachePath ? join8(appRoot, resolved.preset.cachePath) : null;
|
|
15746
|
+
const buildCacheKey = sanitizeCacheKey(input.branch);
|
|
15747
|
+
if (cachePorts && buildCacheDir) {
|
|
15748
|
+
await tryRestore(
|
|
15749
|
+
"next",
|
|
15750
|
+
buildCacheKey,
|
|
15751
|
+
buildCacheDir,
|
|
15458
15752
|
cachePorts,
|
|
15459
15753
|
observer
|
|
15460
15754
|
);
|
|
15461
15755
|
}
|
|
15462
|
-
|
|
15463
|
-
|
|
15464
|
-
|
|
15465
|
-
|
|
15466
|
-
|
|
15467
|
-
|
|
15468
|
-
|
|
15469
|
-
|
|
15470
|
-
|
|
15471
|
-
|
|
15472
|
-
|
|
15473
|
-
|
|
15474
|
-
|
|
15475
|
-
} : {}
|
|
15476
|
-
},
|
|
15477
|
-
timeoutMs: 10 * 6e4,
|
|
15478
|
-
stage: "INSTALLING",
|
|
15479
|
-
errorCode: "INSTALL_FAILED"
|
|
15480
|
-
},
|
|
15481
|
-
observer
|
|
15482
|
-
);
|
|
15483
|
-
await notifyStatus(observer, "BUILDING");
|
|
15484
|
-
const nextCacheDir = join7(appRoot, ".next", "cache");
|
|
15485
|
-
const nextKey = sanitizeCacheKey(input.branch);
|
|
15486
|
-
if (cachePorts) {
|
|
15487
|
-
await tryRestore("next", nextKey, nextCacheDir, cachePorts, observer);
|
|
15488
|
-
}
|
|
15489
|
-
await this.dependencies.runCommand(
|
|
15490
|
-
{
|
|
15491
|
-
command: packageManager.name,
|
|
15492
|
-
args: ["run", project.buildScript],
|
|
15493
|
-
cwd: appRoot,
|
|
15494
|
-
env: {
|
|
15495
|
-
...input.env ?? {},
|
|
15496
|
-
NEXT_PRIVATE_STANDALONE: "true",
|
|
15497
|
-
NPM_CONFIG_USERCONFIG: platformConfig.packageManagerConfigPath
|
|
15498
|
-
},
|
|
15499
|
-
timeoutMs: 15 * 6e4,
|
|
15500
|
-
stage: "BUILDING",
|
|
15501
|
-
errorCode: "BUILD_FAILED"
|
|
15502
|
-
},
|
|
15503
|
-
observer
|
|
15504
|
-
);
|
|
15505
|
-
await this.dependencies.runCommand(
|
|
15506
|
-
{
|
|
15507
|
-
command: this.dependencies.openNextCommand,
|
|
15508
|
-
args: [
|
|
15509
|
-
"build",
|
|
15510
|
-
"--skip-next-build",
|
|
15511
|
-
"--openNextConfigPath",
|
|
15512
|
-
platformConfig.openNextConfigPath,
|
|
15513
|
-
"--config",
|
|
15514
|
-
platformConfig.wranglerConfigPath
|
|
15515
|
-
],
|
|
15516
|
-
cwd: appRoot,
|
|
15517
|
-
timeoutMs: 15 * 6e4,
|
|
15518
|
-
stage: "BUILDING",
|
|
15519
|
-
errorCode: "OPENNEXT_FAILED"
|
|
15520
|
-
},
|
|
15521
|
-
observer
|
|
15522
|
-
);
|
|
15523
|
-
await this.dependencies.runCommand(
|
|
15524
|
-
{
|
|
15525
|
-
command: this.dependencies.wranglerCommand,
|
|
15526
|
-
args: [
|
|
15527
|
-
"deploy",
|
|
15528
|
-
"--dry-run",
|
|
15529
|
-
"--config",
|
|
15530
|
-
platformConfig.wranglerConfigPath,
|
|
15531
|
-
"--outdir",
|
|
15532
|
-
".open-next/worker-bundle"
|
|
15533
|
-
],
|
|
15534
|
-
cwd: appRoot,
|
|
15535
|
-
env: {
|
|
15536
|
-
WRANGLER_LOG_PATH: join7(appRoot, ".open-next", "wrangler.log")
|
|
15756
|
+
await this.dependencies.runCommand(
|
|
15757
|
+
{
|
|
15758
|
+
command: packageManager.name,
|
|
15759
|
+
args: ["run", resolved.buildScript],
|
|
15760
|
+
cwd: appRoot,
|
|
15761
|
+
env: {
|
|
15762
|
+
...input.env,
|
|
15763
|
+
...isNext ? { NEXT_PRIVATE_STANDALONE: "true" } : {},
|
|
15764
|
+
NPM_CONFIG_USERCONFIG: packageManagerConfigPath
|
|
15765
|
+
},
|
|
15766
|
+
timeoutMs: 15 * 6e4,
|
|
15767
|
+
stage: "BUILDING",
|
|
15768
|
+
errorCode: "BUILD_FAILED"
|
|
15537
15769
|
},
|
|
15538
|
-
|
|
15539
|
-
|
|
15540
|
-
|
|
15541
|
-
|
|
15542
|
-
|
|
15543
|
-
|
|
15544
|
-
|
|
15545
|
-
|
|
15546
|
-
|
|
15770
|
+
observer
|
|
15771
|
+
);
|
|
15772
|
+
if (isNext) {
|
|
15773
|
+
await this.dependencies.runCommand(
|
|
15774
|
+
{
|
|
15775
|
+
command: this.dependencies.openNextCommand,
|
|
15776
|
+
args: [
|
|
15777
|
+
"build",
|
|
15778
|
+
"--skip-next-build",
|
|
15779
|
+
"--openNextConfigPath",
|
|
15780
|
+
platformConfig.openNextConfigPath,
|
|
15781
|
+
"--config",
|
|
15782
|
+
platformConfig.wranglerConfigPath
|
|
15783
|
+
],
|
|
15784
|
+
cwd: appRoot,
|
|
15785
|
+
timeoutMs: 15 * 6e4,
|
|
15786
|
+
stage: "BUILDING",
|
|
15787
|
+
errorCode: "OPENNEXT_FAILED"
|
|
15788
|
+
},
|
|
15789
|
+
observer
|
|
15790
|
+
);
|
|
15791
|
+
await this.dependencies.runCommand(
|
|
15792
|
+
{
|
|
15793
|
+
command: this.dependencies.wranglerCommand,
|
|
15794
|
+
args: [
|
|
15795
|
+
"deploy",
|
|
15796
|
+
"--dry-run",
|
|
15797
|
+
"--config",
|
|
15798
|
+
platformConfig.wranglerConfigPath,
|
|
15799
|
+
"--outdir",
|
|
15800
|
+
".open-next/worker-bundle"
|
|
15801
|
+
],
|
|
15802
|
+
cwd: appRoot,
|
|
15803
|
+
env: {
|
|
15804
|
+
WRANGLER_LOG_PATH: join8(appRoot, ".open-next", "wrangler.log")
|
|
15805
|
+
},
|
|
15806
|
+
timeoutMs: 5 * 6e4,
|
|
15807
|
+
stage: "BUILDING",
|
|
15808
|
+
errorCode: "OPENNEXT_FAILED"
|
|
15809
|
+
},
|
|
15810
|
+
observer
|
|
15811
|
+
);
|
|
15812
|
+
}
|
|
15813
|
+
if (cachePorts) {
|
|
15814
|
+
if (depsKey && !depsRestored) {
|
|
15815
|
+
await trySave("deps", depsKey, pmCacheDir, cachePorts, observer);
|
|
15816
|
+
}
|
|
15817
|
+
if (buildCacheDir) {
|
|
15818
|
+
await trySave(
|
|
15819
|
+
"next",
|
|
15820
|
+
buildCacheKey,
|
|
15821
|
+
buildCacheDir,
|
|
15822
|
+
cachePorts,
|
|
15823
|
+
observer
|
|
15824
|
+
);
|
|
15825
|
+
}
|
|
15547
15826
|
}
|
|
15548
|
-
await trySave("next", nextKey, nextCacheDir, cachePorts, observer);
|
|
15549
15827
|
}
|
|
15550
15828
|
await notifyStatus(observer, "DEPLOYING");
|
|
15551
|
-
const
|
|
15829
|
+
const identity = {
|
|
15830
|
+
deploymentId: input.deploymentId,
|
|
15831
|
+
projectId: input.projectId
|
|
15832
|
+
};
|
|
15833
|
+
const artifact = isNext ? await this.dependencies.readOpenNextArtifact(
|
|
15552
15834
|
appRoot,
|
|
15553
|
-
|
|
15554
|
-
deploymentId: input.deploymentId,
|
|
15555
|
-
projectId: input.projectId
|
|
15556
|
-
},
|
|
15835
|
+
identity,
|
|
15557
15836
|
platformConfig.wranglerConfigPath
|
|
15837
|
+
) : await this.dependencies.readStaticArtifact(
|
|
15838
|
+
appRoot,
|
|
15839
|
+
resolved.outputDirectory,
|
|
15840
|
+
identity,
|
|
15841
|
+
{ spaFallback: resolved.preset.spaFallback },
|
|
15842
|
+
(message) => observer.onLog({ level: "WARN", stage: "DEPLOYING", message })
|
|
15558
15843
|
);
|
|
15559
15844
|
return { gitCommitSha, artifact };
|
|
15560
15845
|
} finally {
|
|
@@ -15563,53 +15848,68 @@ var LocalBuildExecutor = class {
|
|
|
15563
15848
|
}
|
|
15564
15849
|
};
|
|
15565
15850
|
|
|
15566
|
-
// src/
|
|
15567
|
-
import {
|
|
15568
|
-
|
|
15569
|
-
|
|
15570
|
-
|
|
15571
|
-
|
|
15572
|
-
|
|
15573
|
-
|
|
15574
|
-
|
|
15575
|
-
|
|
15576
|
-
|
|
15577
|
-
|
|
15578
|
-
|
|
15579
|
-
|
|
15580
|
-
|
|
15581
|
-
|
|
15582
|
-
|
|
15583
|
-
|
|
15584
|
-
|
|
15851
|
+
// ../../packages/sdk/src/auth.ts
|
|
15852
|
+
import { homedir as homedir2 } from "os";
|
|
15853
|
+
|
|
15854
|
+
// ../../packages/sdk/src/errors.ts
|
|
15855
|
+
var NaroError = class extends Error {
|
|
15856
|
+
code;
|
|
15857
|
+
/** HTTP status of the control plane response, when one was received. */
|
|
15858
|
+
status;
|
|
15859
|
+
/** Extra structured context (validation fields, deployment id...). */
|
|
15860
|
+
details;
|
|
15861
|
+
constructor(code, message, options) {
|
|
15862
|
+
super(
|
|
15863
|
+
message,
|
|
15864
|
+
options?.cause !== void 0 ? { cause: options.cause } : {}
|
|
15865
|
+
);
|
|
15866
|
+
this.name = "NaroError";
|
|
15867
|
+
this.code = code;
|
|
15868
|
+
if (options?.status !== void 0) this.status = options.status;
|
|
15869
|
+
if (options?.details !== void 0) this.details = options.details;
|
|
15585
15870
|
}
|
|
15871
|
+
};
|
|
15872
|
+
function isNaroError(error51) {
|
|
15873
|
+
return error51 instanceof NaroError || error51 instanceof Error && error51.name === "NaroError" && typeof error51.code === "string";
|
|
15586
15874
|
}
|
|
15587
|
-
|
|
15588
|
-
|
|
15589
|
-
const
|
|
15590
|
-
|
|
15591
|
-
|
|
15592
|
-
|
|
15875
|
+
function toNaroError(error51, fallbackCode = "INTERNAL_ERROR") {
|
|
15876
|
+
if (error51 instanceof NaroError) return error51;
|
|
15877
|
+
const message = error51 instanceof Error ? error51.message : String(error51);
|
|
15878
|
+
return new NaroError(fallbackCode, message, { cause: error51 });
|
|
15879
|
+
}
|
|
15880
|
+
async function errorFromResponse(response, context) {
|
|
15881
|
+
let body = null;
|
|
15882
|
+
try {
|
|
15883
|
+
body = await response.json();
|
|
15884
|
+
} catch {
|
|
15885
|
+
body = null;
|
|
15886
|
+
}
|
|
15887
|
+
const serverCode = typeof body?.error?.code === "string" ? body.error.code : null;
|
|
15888
|
+
const serverMessage = typeof body?.error?.message === "string" ? body.error.message : null;
|
|
15889
|
+
const code = serverCode ?? (response.status === 401 ? "UNAUTHORIZED" : response.status === 404 ? "NOT_FOUND" : response.status === 429 ? "RATE_LIMITED" : "INTERNAL_ERROR");
|
|
15890
|
+
const details = {};
|
|
15891
|
+
if (body?.error?.fields !== void 0) details.fields = body.error.fields;
|
|
15892
|
+
if (typeof body?.error?.installUrl === "string")
|
|
15893
|
+
details.installUrl = body.error.installUrl;
|
|
15894
|
+
return new NaroError(
|
|
15895
|
+
code,
|
|
15896
|
+
serverMessage ?? `${context} failed: the control plane returned ${String(response.status)}.`,
|
|
15897
|
+
{
|
|
15898
|
+
status: response.status,
|
|
15899
|
+
...Object.keys(details).length > 0 ? { details } : {}
|
|
15900
|
+
}
|
|
15593
15901
|
);
|
|
15594
|
-
return {
|
|
15595
|
-
repositoryUrl: remote !== null && SUPPORTED_REMOTE.some((re) => re.test(remote)) ? remote : null,
|
|
15596
|
-
branch: branch !== null && branch !== "HEAD" ? branch : null
|
|
15597
|
-
};
|
|
15598
15902
|
}
|
|
15599
15903
|
|
|
15600
|
-
// src/
|
|
15601
|
-
import { spawn as spawn2 } from "child_process";
|
|
15602
|
-
import { homedir as homedir2 } from "os";
|
|
15603
|
-
|
|
15604
|
-
// src/session.ts
|
|
15904
|
+
// ../../packages/sdk/src/session.ts
|
|
15605
15905
|
import { mkdirSync, readFileSync, rmSync, writeFileSync } from "fs";
|
|
15606
15906
|
import { homedir } from "os";
|
|
15607
|
-
import { join as
|
|
15907
|
+
import { join as join9 } from "path";
|
|
15608
15908
|
function credentialsDir(home = homedir()) {
|
|
15609
|
-
return
|
|
15909
|
+
return join9(home, ".naro");
|
|
15610
15910
|
}
|
|
15611
15911
|
function credentialsFile(home = homedir()) {
|
|
15612
|
-
return
|
|
15912
|
+
return join9(credentialsDir(home), "credentials.json");
|
|
15613
15913
|
}
|
|
15614
15914
|
function saveSession(session, home = homedir()) {
|
|
15615
15915
|
mkdirSync(credentialsDir(home), { recursive: true, mode: 448 });
|
|
@@ -15633,20 +15933,31 @@ function isExpired(session, skewSeconds = 60) {
|
|
|
15633
15933
|
return session.expires_at * 1e3 <= Date.now() + skewSeconds * 1e3;
|
|
15634
15934
|
}
|
|
15635
15935
|
|
|
15636
|
-
// src/supabase-auth.ts
|
|
15936
|
+
// ../../packages/sdk/src/supabase-auth.ts
|
|
15637
15937
|
import {
|
|
15638
15938
|
createClient
|
|
15639
15939
|
} from "@supabase/supabase-js";
|
|
15640
15940
|
|
|
15641
|
-
// src/
|
|
15941
|
+
// ../../packages/sdk/src/config.ts
|
|
15942
|
+
var DEFAULT_CONTROL_PLANE_URL = "https://naro.sh";
|
|
15943
|
+
var DEFAULT_SUPABASE_URL = "https://bymwzjpqfmwsdhtcpgjg.supabase.co";
|
|
15944
|
+
var DEFAULT_SUPABASE_KEY = "sb_publishable_qjM6NZsHnGZ1t6iwuTDwcQ_LQbq4aCX";
|
|
15945
|
+
function controlPlaneUrl(env = process.env) {
|
|
15946
|
+
return (env.NARO_CONTROL_PLANE_URL ?? DEFAULT_CONTROL_PLANE_URL).replace(
|
|
15947
|
+
/\/$/,
|
|
15948
|
+
""
|
|
15949
|
+
);
|
|
15950
|
+
}
|
|
15951
|
+
|
|
15952
|
+
// ../../packages/sdk/src/callback-server.ts
|
|
15642
15953
|
import { createServer } from "http";
|
|
15643
15954
|
var DONE_HTML = "<html><body><p>\u2705 Logged in. You can close this tab and return to the terminal.</p></body></html>";
|
|
15644
15955
|
var FAIL_HTML = "<html><body><p>Login failed. You can close this tab and return to the terminal.</p></body></html>";
|
|
15645
15956
|
async function startCallbackServer() {
|
|
15646
15957
|
let resolveCode;
|
|
15647
15958
|
let rejectCode;
|
|
15648
|
-
const codePromise = new Promise((
|
|
15649
|
-
resolveCode =
|
|
15959
|
+
const codePromise = new Promise((resolve4, reject) => {
|
|
15960
|
+
resolveCode = resolve4;
|
|
15650
15961
|
rejectCode = reject;
|
|
15651
15962
|
});
|
|
15652
15963
|
const server = createServer((req, res) => {
|
|
@@ -15673,7 +15984,7 @@ async function startCallbackServer() {
|
|
|
15673
15984
|
res.end(DONE_HTML);
|
|
15674
15985
|
resolveCode(code);
|
|
15675
15986
|
});
|
|
15676
|
-
await new Promise((
|
|
15987
|
+
await new Promise((resolve4) => server.listen(0, "127.0.0.1", resolve4));
|
|
15677
15988
|
const port = server.address().port;
|
|
15678
15989
|
return {
|
|
15679
15990
|
port,
|
|
@@ -15696,12 +16007,10 @@ async function startCallbackServer() {
|
|
|
15696
16007
|
};
|
|
15697
16008
|
}
|
|
15698
16009
|
|
|
15699
|
-
// src/supabase-auth.ts
|
|
15700
|
-
var DEFAULT_URL = "https://bymwzjpqfmwsdhtcpgjg.supabase.co";
|
|
15701
|
-
var DEFAULT_KEY = "sb_publishable_qjM6NZsHnGZ1t6iwuTDwcQ_LQbq4aCX";
|
|
16010
|
+
// ../../packages/sdk/src/supabase-auth.ts
|
|
15702
16011
|
function createAuthClient(env = process.env) {
|
|
15703
|
-
const url2 = env.NARO_SUPABASE_URL ??
|
|
15704
|
-
const key = env.NARO_SUPABASE_KEY ??
|
|
16012
|
+
const url2 = env.NARO_SUPABASE_URL ?? DEFAULT_SUPABASE_URL;
|
|
16013
|
+
const key = env.NARO_SUPABASE_KEY ?? DEFAULT_SUPABASE_KEY;
|
|
15705
16014
|
const store = /* @__PURE__ */ new Map();
|
|
15706
16015
|
return createClient(url2, key, {
|
|
15707
16016
|
auth: {
|
|
@@ -15774,69 +16083,873 @@ async function login(deps) {
|
|
|
15774
16083
|
}
|
|
15775
16084
|
}
|
|
15776
16085
|
|
|
15777
|
-
// src/
|
|
15778
|
-
|
|
15779
|
-
|
|
15780
|
-
|
|
15781
|
-
|
|
15782
|
-
|
|
15783
|
-
};
|
|
16086
|
+
// ../../packages/sdk/src/auth.ts
|
|
16087
|
+
async function resolveAuth(options = {}) {
|
|
16088
|
+
const env = options.env ?? process.env;
|
|
16089
|
+
const home = options.home ?? homedir2();
|
|
16090
|
+
if (options.explicitToken) {
|
|
16091
|
+
const token = options.explicitToken;
|
|
16092
|
+
return { getAccessToken: async () => token, source: "flag" };
|
|
16093
|
+
}
|
|
16094
|
+
if (env.NARO_TOKEN) {
|
|
16095
|
+
const token = env.NARO_TOKEN;
|
|
16096
|
+
return { getAccessToken: async () => token, source: "env" };
|
|
16097
|
+
}
|
|
16098
|
+
const refresh = options.refresh ?? refreshStoredSession;
|
|
16099
|
+
let session = loadStoredSession(home);
|
|
16100
|
+
if (!session || isExpired(session)) {
|
|
16101
|
+
if (session) {
|
|
16102
|
+
try {
|
|
16103
|
+
session = await refresh(session.refresh_token);
|
|
16104
|
+
saveSession(session, home);
|
|
16105
|
+
} catch {
|
|
16106
|
+
session = null;
|
|
16107
|
+
}
|
|
16108
|
+
}
|
|
16109
|
+
if (!session) {
|
|
16110
|
+
const interactive = options.interactiveLogin;
|
|
16111
|
+
if (!interactive) {
|
|
16112
|
+
throw new NaroError(
|
|
16113
|
+
"NOT_LOGGED_IN",
|
|
16114
|
+
"Not logged in. Run `naro login`, or set NARO_TOKEN."
|
|
16115
|
+
);
|
|
16116
|
+
}
|
|
16117
|
+
interactive.log(
|
|
16118
|
+
"Not logged in \u2014 opening your browser to sign in with GitHub\u2026"
|
|
16119
|
+
);
|
|
16120
|
+
const loginFlow = options.loginFlow ?? ((openBrowser2) => login({ openBrowser: openBrowser2 }));
|
|
16121
|
+
session = await loginFlow((url2) => {
|
|
16122
|
+
interactive.log(`If your browser did not open, visit:
|
|
16123
|
+
${url2}`);
|
|
16124
|
+
interactive.openBrowser(url2);
|
|
16125
|
+
});
|
|
16126
|
+
saveSession(session, home);
|
|
16127
|
+
}
|
|
16128
|
+
}
|
|
16129
|
+
const auth = {
|
|
16130
|
+
source: "session",
|
|
16131
|
+
user: session.user,
|
|
16132
|
+
getAccessToken: async () => {
|
|
16133
|
+
const current = loadStoredSession(home);
|
|
16134
|
+
if (!current) {
|
|
16135
|
+
throw new NaroError(
|
|
16136
|
+
"NOT_LOGGED_IN",
|
|
16137
|
+
"The session was logged out. Run `naro login` again."
|
|
16138
|
+
);
|
|
16139
|
+
}
|
|
16140
|
+
if (!isExpired(current)) return current.access_token;
|
|
16141
|
+
const refreshed = await refresh(current.refresh_token);
|
|
16142
|
+
saveSession(refreshed, home);
|
|
16143
|
+
return refreshed.access_token;
|
|
16144
|
+
}
|
|
16145
|
+
};
|
|
16146
|
+
return auth;
|
|
16147
|
+
}
|
|
16148
|
+
|
|
16149
|
+
// ../../packages/sdk/src/browser.ts
|
|
16150
|
+
import { spawn as spawn2 } from "child_process";
|
|
15784
16151
|
function openBrowser(url2) {
|
|
15785
16152
|
const platform = process.platform;
|
|
15786
16153
|
const command = platform === "darwin" ? "open" : platform === "win32" ? "cmd" : "xdg-open";
|
|
15787
16154
|
const args = platform === "win32" ? ["/c", "start", "", url2] : [url2];
|
|
15788
|
-
spawn2(command, args, { stdio: "ignore", detached: true })
|
|
15789
|
-
|
|
15790
|
-
async function runLogin(io = stdio) {
|
|
15791
|
-
io.out("Opening your browser to sign in with GitHub...");
|
|
15792
|
-
const session = await login({
|
|
15793
|
-
openBrowser: (url2) => {
|
|
15794
|
-
io.out(`If your browser did not open, visit:
|
|
15795
|
-
${url2}`);
|
|
15796
|
-
openBrowser(url2);
|
|
15797
|
-
}
|
|
16155
|
+
const child = spawn2(command, args, { stdio: "ignore", detached: true });
|
|
16156
|
+
child.on("error", () => {
|
|
15798
16157
|
});
|
|
15799
|
-
|
|
15800
|
-
io.out(`\u2705 Logged in as ${session.user.email ?? session.user.id}`);
|
|
16158
|
+
child.unref();
|
|
15801
16159
|
}
|
|
15802
|
-
|
|
15803
|
-
|
|
15804
|
-
|
|
15805
|
-
|
|
15806
|
-
|
|
15807
|
-
|
|
15808
|
-
|
|
15809
|
-
|
|
15810
|
-
try {
|
|
15811
|
-
session = await refresh(session.refresh_token);
|
|
15812
|
-
saveSession(session, home);
|
|
15813
|
-
} catch {
|
|
15814
|
-
io.err("Session expired. Run `naro login`.");
|
|
15815
|
-
process.exitCode = 1;
|
|
15816
|
-
return;
|
|
15817
|
-
}
|
|
15818
|
-
}
|
|
15819
|
-
const handle = session.user.github ? ` (${session.user.github})` : "";
|
|
15820
|
-
io.out(`Logged in as ${session.user.email ?? session.user.id}${handle}`);
|
|
16160
|
+
|
|
16161
|
+
// ../../packages/sdk/src/types.ts
|
|
16162
|
+
var TERMINAL_DEPLOYMENT_STATUSES = [
|
|
16163
|
+
"READY",
|
|
16164
|
+
"FAILED"
|
|
16165
|
+
];
|
|
16166
|
+
function isTerminalStatus(status) {
|
|
16167
|
+
return TERMINAL_DEPLOYMENT_STATUSES.includes(status);
|
|
15821
16168
|
}
|
|
15822
|
-
|
|
15823
|
-
|
|
15824
|
-
|
|
15825
|
-
|
|
15826
|
-
|
|
15827
|
-
|
|
15828
|
-
|
|
16169
|
+
function projectInfo(row) {
|
|
16170
|
+
return {
|
|
16171
|
+
id: row.id,
|
|
16172
|
+
name: row.name,
|
|
16173
|
+
slug: row.slug,
|
|
16174
|
+
subdomain: row.subdomain,
|
|
16175
|
+
teamId: row.team_id,
|
|
16176
|
+
defaultBranch: row.default_branch,
|
|
16177
|
+
rootDirectory: row.root_directory,
|
|
16178
|
+
repositoryUrl: row.repository_url,
|
|
16179
|
+
repositoryFullName: row.repository_full_name ?? null,
|
|
16180
|
+
createdAt: row.created_at,
|
|
16181
|
+
updatedAt: row.updated_at
|
|
16182
|
+
};
|
|
16183
|
+
}
|
|
16184
|
+
function deploymentInfo(row) {
|
|
16185
|
+
return {
|
|
16186
|
+
id: row.id,
|
|
16187
|
+
projectId: row.project_id,
|
|
16188
|
+
status: row.status,
|
|
16189
|
+
branch: row.branch,
|
|
16190
|
+
url: row.preview_url,
|
|
16191
|
+
artifactKey: row.artifact_key ?? null,
|
|
16192
|
+
errorCode: row.error_code,
|
|
16193
|
+
errorMessage: row.error_message,
|
|
16194
|
+
gitCommitSha: row.git_commit_sha,
|
|
16195
|
+
gitCommitMessage: row.git_commit_message,
|
|
16196
|
+
createdAt: row.created_at,
|
|
16197
|
+
startedAt: row.started_at,
|
|
16198
|
+
finishedAt: row.finished_at
|
|
16199
|
+
};
|
|
16200
|
+
}
|
|
16201
|
+
function buildLogEntry(row) {
|
|
16202
|
+
return {
|
|
16203
|
+
sequence: row.sequence,
|
|
16204
|
+
level: row.level,
|
|
16205
|
+
stage: row.stage,
|
|
16206
|
+
message: row.message,
|
|
16207
|
+
createdAt: row.created_at
|
|
16208
|
+
};
|
|
15829
16209
|
}
|
|
15830
|
-
|
|
15831
|
-
|
|
15832
|
-
|
|
16210
|
+
|
|
16211
|
+
// ../../packages/sdk/src/client.ts
|
|
16212
|
+
var NaroClient = class {
|
|
16213
|
+
baseUrl;
|
|
16214
|
+
getAccessToken;
|
|
16215
|
+
doFetch;
|
|
16216
|
+
teams;
|
|
16217
|
+
projects;
|
|
16218
|
+
github;
|
|
16219
|
+
deployments;
|
|
16220
|
+
env;
|
|
16221
|
+
domains;
|
|
16222
|
+
logs;
|
|
16223
|
+
constructor(options) {
|
|
16224
|
+
this.baseUrl = (options.baseUrl ?? controlPlaneUrl()).replace(/\/$/, "");
|
|
16225
|
+
this.getAccessToken = options.getAccessToken;
|
|
16226
|
+
this.doFetch = options.fetch ?? fetch;
|
|
16227
|
+
this.teams = new TeamsApi(this);
|
|
16228
|
+
this.projects = new ProjectsApi(this);
|
|
16229
|
+
this.github = new GithubApi(this);
|
|
16230
|
+
this.deployments = new DeploymentsApi(this);
|
|
16231
|
+
this.env = new EnvApi(this);
|
|
16232
|
+
this.domains = new DomainsApi(this);
|
|
16233
|
+
this.logs = new LogsApi(this);
|
|
16234
|
+
}
|
|
16235
|
+
async request(path, options) {
|
|
16236
|
+
const token = await this.getAccessToken();
|
|
16237
|
+
let response;
|
|
15833
16238
|
try {
|
|
15834
|
-
await
|
|
16239
|
+
response = await this.doFetch(`${this.baseUrl}${path}`, {
|
|
16240
|
+
method: options.method ?? "GET",
|
|
16241
|
+
headers: {
|
|
16242
|
+
Authorization: `Bearer ${token}`,
|
|
16243
|
+
...options.body !== void 0 ? { "Content-Type": "application/json" } : {}
|
|
16244
|
+
},
|
|
16245
|
+
...options.body !== void 0 ? { body: JSON.stringify(options.body) } : {}
|
|
16246
|
+
});
|
|
16247
|
+
} catch (error51) {
|
|
16248
|
+
throw new NaroError(
|
|
16249
|
+
"NETWORK_ERROR",
|
|
16250
|
+
`${options.context} failed: could not reach ${this.baseUrl}.`,
|
|
16251
|
+
{ cause: error51 }
|
|
16252
|
+
);
|
|
16253
|
+
}
|
|
16254
|
+
if (!response.ok) throw await errorFromResponse(response, options.context);
|
|
16255
|
+
return await response.json();
|
|
16256
|
+
}
|
|
16257
|
+
};
|
|
16258
|
+
var TeamsApi = class {
|
|
16259
|
+
constructor(client) {
|
|
16260
|
+
this.client = client;
|
|
16261
|
+
}
|
|
16262
|
+
async list() {
|
|
16263
|
+
const body = await this.client.request(
|
|
16264
|
+
"/api/teams",
|
|
16265
|
+
{ context: "List teams" }
|
|
16266
|
+
);
|
|
16267
|
+
return body.teams;
|
|
16268
|
+
}
|
|
16269
|
+
};
|
|
16270
|
+
var ProjectsApi = class {
|
|
16271
|
+
constructor(client) {
|
|
16272
|
+
this.client = client;
|
|
16273
|
+
}
|
|
16274
|
+
async list(teamSlug) {
|
|
16275
|
+
const body = await this.client.request(
|
|
16276
|
+
`/api/projects?team=${encodeURIComponent(teamSlug)}`,
|
|
16277
|
+
{ context: "List projects" }
|
|
16278
|
+
);
|
|
16279
|
+
return body.projects.map(projectInfo);
|
|
16280
|
+
}
|
|
16281
|
+
async get(projectId) {
|
|
16282
|
+
const body = await this.client.request(
|
|
16283
|
+
`/api/projects/${encodeURIComponent(projectId)}`,
|
|
16284
|
+
{ context: "Read project" }
|
|
16285
|
+
);
|
|
16286
|
+
return projectInfo(body.project);
|
|
16287
|
+
}
|
|
16288
|
+
/**
|
|
16289
|
+
* Connects a project to a GitHub repository, turning `git push` into a
|
|
16290
|
+
* deployment.
|
|
16291
|
+
*
|
|
16292
|
+
* "The App is not installed on that account" is a normal branch here, not a
|
|
16293
|
+
* failure: the CLI opens a browser and polls, while MCP hands the URL back
|
|
16294
|
+
* to the agent. So it is returned rather than thrown.
|
|
16295
|
+
*/
|
|
16296
|
+
async connectGit(projectId, fullName) {
|
|
16297
|
+
try {
|
|
16298
|
+
const body = await this.client.request(
|
|
16299
|
+
`/api/projects/${encodeURIComponent(projectId)}/git`,
|
|
16300
|
+
{ method: "POST", body: { fullName }, context: "Connect repository" }
|
|
16301
|
+
);
|
|
16302
|
+
return { ok: true, project: projectInfo(body.project) };
|
|
16303
|
+
} catch (error51) {
|
|
16304
|
+
if (isNaroError(error51) && error51.code === "REPOSITORY_NOT_ACCESSIBLE") {
|
|
16305
|
+
const installUrl = error51.details?.installUrl;
|
|
16306
|
+
return {
|
|
16307
|
+
ok: false,
|
|
16308
|
+
installUrl: typeof installUrl === "string" ? installUrl : null
|
|
16309
|
+
};
|
|
16310
|
+
}
|
|
16311
|
+
throw error51;
|
|
16312
|
+
}
|
|
16313
|
+
}
|
|
16314
|
+
async disconnectGit(projectId) {
|
|
16315
|
+
const body = await this.client.request(
|
|
16316
|
+
`/api/projects/${encodeURIComponent(projectId)}/git`,
|
|
16317
|
+
{ method: "DELETE", context: "Disconnect repository" }
|
|
16318
|
+
);
|
|
16319
|
+
return projectInfo(body.project);
|
|
16320
|
+
}
|
|
16321
|
+
};
|
|
16322
|
+
var GithubApi = class {
|
|
16323
|
+
constructor(client) {
|
|
16324
|
+
this.client = client;
|
|
16325
|
+
}
|
|
16326
|
+
/** Repositories every installation attached to the team can reach. */
|
|
16327
|
+
async listRepositories(teamSlug) {
|
|
16328
|
+
const body = await this.client.request(`/api/github/repositories?team=${encodeURIComponent(teamSlug)}`, {
|
|
16329
|
+
context: "List repositories"
|
|
16330
|
+
});
|
|
16331
|
+
return body.repositories;
|
|
16332
|
+
}
|
|
16333
|
+
};
|
|
16334
|
+
var DeploymentsApi = class {
|
|
16335
|
+
constructor(client) {
|
|
16336
|
+
this.client = client;
|
|
16337
|
+
}
|
|
16338
|
+
async list(projectId) {
|
|
16339
|
+
const body = await this.client.request(`/api/projects/${encodeURIComponent(projectId)}/deployments`, {
|
|
16340
|
+
context: "List deployments"
|
|
16341
|
+
});
|
|
16342
|
+
return body.deployments.map(deploymentInfo);
|
|
16343
|
+
}
|
|
16344
|
+
async get(deploymentId) {
|
|
16345
|
+
const body = await this.client.request(`/api/deployments/${encodeURIComponent(deploymentId)}`, {
|
|
16346
|
+
context: "Read deployment"
|
|
16347
|
+
});
|
|
16348
|
+
return {
|
|
16349
|
+
deployment: deploymentInfo(body.deployment),
|
|
16350
|
+
logs: body.logs.map(buildLogEntry)
|
|
16351
|
+
};
|
|
16352
|
+
}
|
|
16353
|
+
/**
|
|
16354
|
+
* Starts a CLI deployment: the control plane creates/loads the project,
|
|
16355
|
+
* records the deployment and returns a callback token; the caller builds
|
|
16356
|
+
* locally and uploads the artifact.
|
|
16357
|
+
*/
|
|
16358
|
+
async createCli(input) {
|
|
16359
|
+
return this.client.request("/api/cli/deployments", {
|
|
16360
|
+
method: "POST",
|
|
16361
|
+
body: input,
|
|
16362
|
+
context: "Create deployment"
|
|
16363
|
+
});
|
|
16364
|
+
}
|
|
16365
|
+
/**
|
|
16366
|
+
* Queues a server-side git build (GitHub-connected projects): clone,
|
|
16367
|
+
* build and deploy run on the platform.
|
|
16368
|
+
*/
|
|
16369
|
+
async createRemote(projectId, options = {}) {
|
|
16370
|
+
const body = await this.client.request(
|
|
16371
|
+
`/api/projects/${encodeURIComponent(projectId)}/deployments`,
|
|
16372
|
+
{
|
|
16373
|
+
method: "POST",
|
|
16374
|
+
// createDeploymentSchema accepts string | undefined — never null.
|
|
16375
|
+
body: options.branch ? { branch: options.branch } : {},
|
|
16376
|
+
context: "Create deployment"
|
|
16377
|
+
}
|
|
16378
|
+
);
|
|
16379
|
+
return deploymentInfo(body.deployment);
|
|
16380
|
+
}
|
|
16381
|
+
/**
|
|
16382
|
+
* Picks the deployment `naro rollback` restores: the newest READY
|
|
16383
|
+
* production deployment serving a DIFFERENT artifact than the current one.
|
|
16384
|
+
* Comparing artifacts (not row order) keeps repeated rollbacks from
|
|
16385
|
+
* ping-ponging between the same two rows — promoted rows share their
|
|
16386
|
+
* source's artifact.
|
|
16387
|
+
*/
|
|
16388
|
+
async rollbackTarget(projectId, defaultBranch) {
|
|
16389
|
+
const deployments = await this.list(projectId);
|
|
16390
|
+
const production = deployments.filter(
|
|
16391
|
+
(deployment) => deployment.status === "READY" && deployment.branch === defaultBranch
|
|
16392
|
+
);
|
|
16393
|
+
const current = production[0];
|
|
16394
|
+
if (!current) {
|
|
16395
|
+
throw new NaroError(
|
|
16396
|
+
"DEPLOYMENT_NOT_FOUND",
|
|
16397
|
+
"No production deployment to roll back from."
|
|
16398
|
+
);
|
|
16399
|
+
}
|
|
16400
|
+
const differs = (candidate) => current.artifactKey && candidate.artifactKey ? candidate.artifactKey !== current.artifactKey : candidate.id !== current.id;
|
|
16401
|
+
return { current, target: production.slice(1).find(differs) ?? null };
|
|
16402
|
+
}
|
|
16403
|
+
/**
|
|
16404
|
+
* Redeploys an existing READY deployment's artifact to production.
|
|
16405
|
+
* Used by both `naro promote` (preview → production) and `naro rollback`
|
|
16406
|
+
* (older production build → production).
|
|
16407
|
+
*/
|
|
16408
|
+
async promote(projectId, deploymentId) {
|
|
16409
|
+
const body = await this.client.request(
|
|
16410
|
+
`/api/projects/${encodeURIComponent(projectId)}/deployments/${encodeURIComponent(deploymentId)}/promote`,
|
|
16411
|
+
{ method: "POST", context: "Promote deployment" }
|
|
16412
|
+
);
|
|
16413
|
+
return deploymentInfo(body.deployment);
|
|
16414
|
+
}
|
|
16415
|
+
/** Polls a deployment until READY/FAILED, streaming new build logs. */
|
|
16416
|
+
async wait(deploymentId, options = {}) {
|
|
16417
|
+
const intervalMs = options.intervalMs ?? 2500;
|
|
16418
|
+
const timeoutMs = options.timeoutMs ?? 15 * 6e4;
|
|
16419
|
+
const sleep = options.sleep ?? ((ms) => new Promise((resolve4) => setTimeout(resolve4, ms)));
|
|
16420
|
+
const startedAt = Date.now();
|
|
16421
|
+
let lastStatus = null;
|
|
16422
|
+
let lastSequence = -1;
|
|
16423
|
+
for (; ; ) {
|
|
16424
|
+
const detail = await this.get(deploymentId);
|
|
16425
|
+
for (const log2 of detail.logs) {
|
|
16426
|
+
if (log2.sequence > lastSequence) {
|
|
16427
|
+
lastSequence = log2.sequence;
|
|
16428
|
+
options.onLog?.(log2);
|
|
16429
|
+
}
|
|
16430
|
+
}
|
|
16431
|
+
if (detail.deployment.status !== lastStatus) {
|
|
16432
|
+
lastStatus = detail.deployment.status;
|
|
16433
|
+
options.onStatus?.(lastStatus);
|
|
16434
|
+
}
|
|
16435
|
+
if (isTerminalStatus(detail.deployment.status)) return detail;
|
|
16436
|
+
if (Date.now() - startedAt > timeoutMs) {
|
|
16437
|
+
throw new NaroError(
|
|
16438
|
+
"TIMEOUT",
|
|
16439
|
+
`Deployment ${deploymentId} did not finish within ${String(Math.round(timeoutMs / 1e3))}s.`,
|
|
16440
|
+
{ details: { deploymentId } }
|
|
16441
|
+
);
|
|
16442
|
+
}
|
|
16443
|
+
await sleep(intervalMs);
|
|
16444
|
+
}
|
|
16445
|
+
}
|
|
16446
|
+
};
|
|
16447
|
+
var EnvApi = class {
|
|
16448
|
+
constructor(client) {
|
|
16449
|
+
this.client = client;
|
|
16450
|
+
}
|
|
16451
|
+
async list(projectId, options = {}) {
|
|
16452
|
+
const query = options.reveal ? "?reveal=true" : "";
|
|
16453
|
+
const body = await this.client.request(
|
|
16454
|
+
`/api/projects/${encodeURIComponent(projectId)}/env${query}`,
|
|
16455
|
+
{ context: "List environment variables" }
|
|
16456
|
+
);
|
|
16457
|
+
return body.envVars;
|
|
16458
|
+
}
|
|
16459
|
+
async set(projectId, entries) {
|
|
16460
|
+
await this.client.request(
|
|
16461
|
+
`/api/projects/${encodeURIComponent(projectId)}/env`,
|
|
16462
|
+
{
|
|
16463
|
+
method: "POST",
|
|
16464
|
+
body: {
|
|
16465
|
+
entries: entries.map((entry) => ({
|
|
16466
|
+
key: entry.key,
|
|
16467
|
+
value: entry.value,
|
|
16468
|
+
target: entry.target ?? "all",
|
|
16469
|
+
isSecret: entry.isSecret ?? true
|
|
16470
|
+
}))
|
|
16471
|
+
},
|
|
16472
|
+
context: "Set environment variable"
|
|
16473
|
+
}
|
|
16474
|
+
);
|
|
16475
|
+
}
|
|
16476
|
+
async removeById(projectId, envVarId) {
|
|
16477
|
+
await this.client.request(
|
|
16478
|
+
`/api/projects/${encodeURIComponent(projectId)}/env/${encodeURIComponent(envVarId)}`,
|
|
16479
|
+
{ method: "DELETE", context: "Remove environment variable" }
|
|
16480
|
+
);
|
|
16481
|
+
}
|
|
16482
|
+
/** Removes every row for a key (optionally narrowed to one target). */
|
|
16483
|
+
async remove(projectId, key, target) {
|
|
16484
|
+
const rows = await this.list(projectId);
|
|
16485
|
+
const matches = rows.filter(
|
|
16486
|
+
(row) => row.key === key && (target === void 0 || row.target === target)
|
|
16487
|
+
);
|
|
16488
|
+
if (matches.length === 0) {
|
|
16489
|
+
throw new NaroError(
|
|
16490
|
+
"ENV_NOT_FOUND",
|
|
16491
|
+
target ? `No ${key} variable found for the ${target} environment.` : `No ${key} variable found.`,
|
|
16492
|
+
{ details: { key, ...target ? { target } : {} } }
|
|
16493
|
+
);
|
|
16494
|
+
}
|
|
16495
|
+
for (const row of matches) await this.removeById(projectId, row.id);
|
|
16496
|
+
return matches.length;
|
|
16497
|
+
}
|
|
16498
|
+
};
|
|
16499
|
+
var DomainsApi = class {
|
|
16500
|
+
constructor(client) {
|
|
16501
|
+
this.client = client;
|
|
16502
|
+
}
|
|
16503
|
+
async list(projectId) {
|
|
16504
|
+
const body = await this.client.request(
|
|
16505
|
+
`/api/projects/${encodeURIComponent(projectId)}/domains`,
|
|
16506
|
+
{ context: "List domains" }
|
|
16507
|
+
);
|
|
16508
|
+
return body.domains;
|
|
16509
|
+
}
|
|
16510
|
+
async add(projectId, hostname3) {
|
|
16511
|
+
const body = await this.client.request(
|
|
16512
|
+
`/api/projects/${encodeURIComponent(projectId)}/domains`,
|
|
16513
|
+
{ method: "POST", body: { hostname: hostname3 }, context: "Add domain" }
|
|
16514
|
+
);
|
|
16515
|
+
return body.domain;
|
|
16516
|
+
}
|
|
16517
|
+
async refresh(projectId, domainId) {
|
|
16518
|
+
const body = await this.client.request(
|
|
16519
|
+
`/api/projects/${encodeURIComponent(projectId)}/domains/${encodeURIComponent(domainId)}`,
|
|
16520
|
+
{ method: "PATCH", context: "Refresh domain" }
|
|
16521
|
+
);
|
|
16522
|
+
return body.domain;
|
|
16523
|
+
}
|
|
16524
|
+
async removeById(projectId, domainId) {
|
|
16525
|
+
await this.client.request(
|
|
16526
|
+
`/api/projects/${encodeURIComponent(projectId)}/domains/${encodeURIComponent(domainId)}`,
|
|
16527
|
+
{ method: "DELETE", context: "Remove domain" }
|
|
16528
|
+
);
|
|
16529
|
+
}
|
|
16530
|
+
/** Removes a domain by hostname. */
|
|
16531
|
+
async remove(projectId, hostname3) {
|
|
16532
|
+
const domains = await this.list(projectId);
|
|
16533
|
+
const match = domains.find((domain2) => domain2.hostname === hostname3);
|
|
16534
|
+
if (!match) {
|
|
16535
|
+
throw new NaroError(
|
|
16536
|
+
"DOMAIN_NOT_FOUND",
|
|
16537
|
+
`${hostname3} is not attached to this project.`,
|
|
16538
|
+
{ details: { hostname: hostname3 } }
|
|
16539
|
+
);
|
|
16540
|
+
}
|
|
16541
|
+
await this.removeById(projectId, match.id);
|
|
16542
|
+
}
|
|
16543
|
+
};
|
|
16544
|
+
var LogsApi = class {
|
|
16545
|
+
constructor(client) {
|
|
16546
|
+
this.client = client;
|
|
16547
|
+
}
|
|
16548
|
+
async runtime(projectId, options = {}) {
|
|
16549
|
+
const params = new URLSearchParams();
|
|
16550
|
+
params.set("env", options.environment ?? "production");
|
|
16551
|
+
if (options.level) params.set("level", options.level);
|
|
16552
|
+
const body = await this.client.request(
|
|
16553
|
+
`/api/projects/${encodeURIComponent(projectId)}/logs?${params.toString()}`,
|
|
16554
|
+
{ context: "Read runtime logs" }
|
|
16555
|
+
);
|
|
16556
|
+
return body.entries;
|
|
16557
|
+
}
|
|
16558
|
+
};
|
|
16559
|
+
|
|
16560
|
+
// ../../packages/sdk/src/dotenv.ts
|
|
16561
|
+
import { existsSync, readFileSync as readFileSync2 } from "fs";
|
|
16562
|
+
import { join as join10 } from "path";
|
|
16563
|
+
function parseDotenv(text) {
|
|
16564
|
+
const out = {};
|
|
16565
|
+
for (const line of text.split(/\r?\n/)) {
|
|
16566
|
+
const trimmed = line.trim();
|
|
16567
|
+
if (!trimmed || trimmed.startsWith("#")) continue;
|
|
16568
|
+
const eq = trimmed.indexOf("=");
|
|
16569
|
+
if (eq <= 0) continue;
|
|
16570
|
+
const key = trimmed.slice(0, eq).trim();
|
|
16571
|
+
let value = trimmed.slice(eq + 1).trim();
|
|
16572
|
+
if (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'"))
|
|
16573
|
+
value = value.slice(1, -1);
|
|
16574
|
+
out[key] = value;
|
|
16575
|
+
}
|
|
16576
|
+
return out;
|
|
16577
|
+
}
|
|
16578
|
+
function readProjectEnv(dir) {
|
|
16579
|
+
for (const name of [".env.local", ".env.production", ".env"]) {
|
|
16580
|
+
const path = join10(dir, name);
|
|
16581
|
+
if (existsSync(path)) return parseDotenv(readFileSync2(path, "utf8"));
|
|
16582
|
+
}
|
|
16583
|
+
return {};
|
|
16584
|
+
}
|
|
16585
|
+
function serializeDotenv(vars) {
|
|
16586
|
+
const needsQuotes = /[\s"'#$`\\]/;
|
|
16587
|
+
return `${Object.entries(vars).map(
|
|
16588
|
+
([key, value]) => needsQuotes.test(value) ? `${key}="${value.replaceAll("\\", "\\\\").replaceAll('"', '\\"')}"` : `${key}=${value}`
|
|
16589
|
+
).join("\n")}
|
|
16590
|
+
`;
|
|
16591
|
+
}
|
|
16592
|
+
|
|
16593
|
+
// ../../packages/sdk/src/env-scan.ts
|
|
16594
|
+
import { existsSync as existsSync2, readFileSync as readFileSync3, readdirSync, statSync } from "fs";
|
|
16595
|
+
import { join as join11 } from "path";
|
|
16596
|
+
var SOURCE_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
16597
|
+
".ts",
|
|
16598
|
+
".tsx",
|
|
16599
|
+
".js",
|
|
16600
|
+
".jsx",
|
|
16601
|
+
".mjs",
|
|
16602
|
+
".cjs",
|
|
16603
|
+
".mts",
|
|
16604
|
+
".cts",
|
|
16605
|
+
".vue",
|
|
16606
|
+
".svelte",
|
|
16607
|
+
".astro"
|
|
16608
|
+
]);
|
|
16609
|
+
var SKIP_DIRECTORIES = /* @__PURE__ */ new Set([
|
|
16610
|
+
"node_modules",
|
|
16611
|
+
"dist",
|
|
16612
|
+
"build",
|
|
16613
|
+
"out",
|
|
16614
|
+
"coverage",
|
|
16615
|
+
"tmp"
|
|
16616
|
+
]);
|
|
16617
|
+
var EXAMPLE_FILES = [".env.example", ".env.sample", ".env.template"];
|
|
16618
|
+
var IGNORED_KEYS = /* @__PURE__ */ new Set([
|
|
16619
|
+
"NODE_ENV",
|
|
16620
|
+
"PORT",
|
|
16621
|
+
"CI",
|
|
16622
|
+
"HOME",
|
|
16623
|
+
"PATH",
|
|
16624
|
+
"PWD",
|
|
16625
|
+
"TZ",
|
|
16626
|
+
"NEXT_RUNTIME",
|
|
16627
|
+
"MODE",
|
|
16628
|
+
"BASE_URL",
|
|
16629
|
+
"DEV",
|
|
16630
|
+
"PROD",
|
|
16631
|
+
"SSR"
|
|
16632
|
+
]);
|
|
16633
|
+
var MAX_FILES = 5e3;
|
|
16634
|
+
var MAX_FILE_BYTES2 = 1024 * 1024;
|
|
16635
|
+
var REFERENCE = /(?:process|import\.meta)\.env(?:\.([A-Za-z_][A-Za-z0-9_]*)|\[\s*["'`]([A-Za-z_][A-Za-z0-9_]*)["'`]\s*\])/g;
|
|
16636
|
+
function ignored(key) {
|
|
16637
|
+
return IGNORED_KEYS.has(key) || key.startsWith("npm_");
|
|
16638
|
+
}
|
|
16639
|
+
function collect(dir, out) {
|
|
16640
|
+
if (out.length >= MAX_FILES) return;
|
|
16641
|
+
let entries;
|
|
16642
|
+
try {
|
|
16643
|
+
entries = readdirSync(dir, { withFileTypes: true });
|
|
16644
|
+
} catch {
|
|
16645
|
+
return;
|
|
16646
|
+
}
|
|
16647
|
+
for (const entry of entries) {
|
|
16648
|
+
if (out.length >= MAX_FILES) return;
|
|
16649
|
+
if (entry.isSymbolicLink()) continue;
|
|
16650
|
+
const path = join11(dir, entry.name);
|
|
16651
|
+
if (entry.isDirectory()) {
|
|
16652
|
+
if (entry.name.startsWith(".") || SKIP_DIRECTORIES.has(entry.name))
|
|
16653
|
+
continue;
|
|
16654
|
+
collect(path, out);
|
|
16655
|
+
continue;
|
|
16656
|
+
}
|
|
16657
|
+
if (!entry.isFile()) continue;
|
|
16658
|
+
const dot = entry.name.lastIndexOf(".");
|
|
16659
|
+
if (dot < 0 || !SOURCE_EXTENSIONS.has(entry.name.slice(dot))) continue;
|
|
16660
|
+
out.push(path);
|
|
16661
|
+
}
|
|
16662
|
+
}
|
|
16663
|
+
function scanEnvReferences(dir) {
|
|
16664
|
+
const found = /* @__PURE__ */ new Map();
|
|
16665
|
+
const files = [];
|
|
16666
|
+
collect(dir, files);
|
|
16667
|
+
for (const file2 of files) {
|
|
16668
|
+
let text;
|
|
16669
|
+
try {
|
|
16670
|
+
if (statSync(file2).size > MAX_FILE_BYTES2) continue;
|
|
16671
|
+
text = readFileSync3(file2, "utf8");
|
|
15835
16672
|
} catch {
|
|
16673
|
+
continue;
|
|
16674
|
+
}
|
|
16675
|
+
for (const match of text.matchAll(REFERENCE)) {
|
|
16676
|
+
const key = match[1] ?? match[2];
|
|
16677
|
+
if (!key || ignored(key)) continue;
|
|
16678
|
+
found.set(key, "code");
|
|
15836
16679
|
}
|
|
15837
16680
|
}
|
|
15838
|
-
|
|
15839
|
-
|
|
16681
|
+
for (const name of EXAMPLE_FILES) {
|
|
16682
|
+
const path = join11(dir, name);
|
|
16683
|
+
if (!existsSync2(path)) continue;
|
|
16684
|
+
let parsed;
|
|
16685
|
+
try {
|
|
16686
|
+
parsed = parseDotenv(readFileSync3(path, "utf8"));
|
|
16687
|
+
} catch {
|
|
16688
|
+
continue;
|
|
16689
|
+
}
|
|
16690
|
+
for (const key of Object.keys(parsed)) {
|
|
16691
|
+
if (ignored(key) || found.has(key)) continue;
|
|
16692
|
+
found.set(key, "example");
|
|
16693
|
+
}
|
|
16694
|
+
}
|
|
16695
|
+
return [...found].map(([key, source]) => ({ key, source })).sort((a, b) => a.key.localeCompare(b.key));
|
|
16696
|
+
}
|
|
16697
|
+
|
|
16698
|
+
// ../../packages/sdk/src/framework.ts
|
|
16699
|
+
import { existsSync as existsSync3, readFileSync as readFileSync4 } from "fs";
|
|
16700
|
+
import { join as join12 } from "path";
|
|
16701
|
+
function readPackageJson2(dir) {
|
|
16702
|
+
try {
|
|
16703
|
+
return JSON.parse(
|
|
16704
|
+
readFileSync4(join12(dir, "package.json"), "utf8")
|
|
16705
|
+
);
|
|
16706
|
+
} catch {
|
|
16707
|
+
return null;
|
|
16708
|
+
}
|
|
16709
|
+
}
|
|
16710
|
+
function hasDependency(pkg, name) {
|
|
16711
|
+
return Boolean(pkg?.dependencies?.[name] ?? pkg?.devDependencies?.[name]);
|
|
16712
|
+
}
|
|
16713
|
+
function hasConfig(dir, base2, evidence) {
|
|
16714
|
+
for (const ext of ["js", "mjs", "cjs", "ts", "mts"]) {
|
|
16715
|
+
if (existsSync3(join12(dir, `${base2}.${ext}`))) {
|
|
16716
|
+
evidence.push(`${base2}.${ext}`);
|
|
16717
|
+
return true;
|
|
16718
|
+
}
|
|
16719
|
+
}
|
|
16720
|
+
return false;
|
|
16721
|
+
}
|
|
16722
|
+
function detectFramework2(dir) {
|
|
16723
|
+
const evidence = [];
|
|
16724
|
+
const pkg = readPackageJson2(dir);
|
|
16725
|
+
if (hasDependency(pkg, "next") || hasConfig(dir, "next.config", evidence)) {
|
|
16726
|
+
if (hasDependency(pkg, "next")) evidence.push("package.json: next");
|
|
16727
|
+
if (existsSync3(join12(dir, "app"))) evidence.push("app/");
|
|
16728
|
+
if (existsSync3(join12(dir, "pages"))) evidence.push("pages/");
|
|
16729
|
+
return {
|
|
16730
|
+
framework: "nextjs",
|
|
16731
|
+
name: "Next.js",
|
|
16732
|
+
buildCommand: "next build",
|
|
16733
|
+
outputDirectory: ".next",
|
|
16734
|
+
evidence,
|
|
16735
|
+
deployable: true
|
|
16736
|
+
};
|
|
16737
|
+
}
|
|
16738
|
+
if (hasDependency(pkg, "nuxt") || hasConfig(dir, "nuxt.config", evidence)) {
|
|
16739
|
+
if (hasDependency(pkg, "nuxt")) evidence.push("package.json: nuxt");
|
|
16740
|
+
return {
|
|
16741
|
+
framework: "nuxt",
|
|
16742
|
+
name: "Nuxt",
|
|
16743
|
+
buildCommand: "nuxt build",
|
|
16744
|
+
outputDirectory: ".output",
|
|
16745
|
+
evidence,
|
|
16746
|
+
deployable: false
|
|
16747
|
+
};
|
|
16748
|
+
}
|
|
16749
|
+
if (hasDependency(pkg, "astro") || hasConfig(dir, "astro.config", evidence)) {
|
|
16750
|
+
if (hasDependency(pkg, "astro")) evidence.push("package.json: astro");
|
|
16751
|
+
return {
|
|
16752
|
+
framework: "astro",
|
|
16753
|
+
name: "Astro",
|
|
16754
|
+
buildCommand: "astro build",
|
|
16755
|
+
outputDirectory: "dist",
|
|
16756
|
+
evidence,
|
|
16757
|
+
deployable: false
|
|
16758
|
+
};
|
|
16759
|
+
}
|
|
16760
|
+
if (hasDependency(pkg, "vite") || hasConfig(dir, "vite.config", evidence)) {
|
|
16761
|
+
if (hasDependency(pkg, "vite")) evidence.push("package.json: vite");
|
|
16762
|
+
return {
|
|
16763
|
+
framework: "vite",
|
|
16764
|
+
name: "Vite",
|
|
16765
|
+
buildCommand: "vite build",
|
|
16766
|
+
outputDirectory: "dist",
|
|
16767
|
+
evidence,
|
|
16768
|
+
deployable: true
|
|
16769
|
+
};
|
|
16770
|
+
}
|
|
16771
|
+
if (hasDependency(pkg, "react-scripts")) {
|
|
16772
|
+
evidence.push("package.json: react-scripts");
|
|
16773
|
+
return {
|
|
16774
|
+
framework: "react",
|
|
16775
|
+
name: "Create React App",
|
|
16776
|
+
buildCommand: "react-scripts build",
|
|
16777
|
+
outputDirectory: "build",
|
|
16778
|
+
evidence,
|
|
16779
|
+
deployable: true
|
|
16780
|
+
};
|
|
16781
|
+
}
|
|
16782
|
+
if (!pkg && existsSync3(join12(dir, "index.html"))) {
|
|
16783
|
+
evidence.push("index.html (no package.json)");
|
|
16784
|
+
return {
|
|
16785
|
+
framework: "static",
|
|
16786
|
+
name: "Static site",
|
|
16787
|
+
buildCommand: null,
|
|
16788
|
+
outputDirectory: ".",
|
|
16789
|
+
evidence,
|
|
16790
|
+
deployable: true
|
|
16791
|
+
};
|
|
16792
|
+
}
|
|
16793
|
+
return {
|
|
16794
|
+
framework: "unknown",
|
|
16795
|
+
name: "Unknown",
|
|
16796
|
+
buildCommand: null,
|
|
16797
|
+
outputDirectory: null,
|
|
16798
|
+
evidence,
|
|
16799
|
+
deployable: false
|
|
16800
|
+
};
|
|
16801
|
+
}
|
|
16802
|
+
|
|
16803
|
+
// ../../packages/sdk/src/link.ts
|
|
16804
|
+
import {
|
|
16805
|
+
existsSync as existsSync4,
|
|
16806
|
+
mkdirSync as mkdirSync2,
|
|
16807
|
+
readFileSync as readFileSync5,
|
|
16808
|
+
rmSync as rmSync2,
|
|
16809
|
+
writeFileSync as writeFileSync2
|
|
16810
|
+
} from "fs";
|
|
16811
|
+
import { join as join13 } from "path";
|
|
16812
|
+
function linkDir(dir) {
|
|
16813
|
+
return join13(dir, ".naro");
|
|
16814
|
+
}
|
|
16815
|
+
function linkFile(dir) {
|
|
16816
|
+
return join13(linkDir(dir), "project.json");
|
|
16817
|
+
}
|
|
16818
|
+
function readProjectLink(dir) {
|
|
16819
|
+
try {
|
|
16820
|
+
const parsed = JSON.parse(readFileSync5(linkFile(dir), "utf8"));
|
|
16821
|
+
if (typeof parsed.projectId !== "string" || parsed.projectId.length === 0)
|
|
16822
|
+
return null;
|
|
16823
|
+
return {
|
|
16824
|
+
projectId: parsed.projectId,
|
|
16825
|
+
...typeof parsed.teamSlug === "string" ? { teamSlug: parsed.teamSlug } : {},
|
|
16826
|
+
...typeof parsed.projectName === "string" ? { projectName: parsed.projectName } : {},
|
|
16827
|
+
...parsed.autoDeployDeclined === true ? { autoDeployDeclined: true } : {}
|
|
16828
|
+
};
|
|
16829
|
+
} catch {
|
|
16830
|
+
return null;
|
|
16831
|
+
}
|
|
16832
|
+
}
|
|
16833
|
+
function writeProjectLink(dir, link) {
|
|
16834
|
+
mkdirSync2(linkDir(dir), { recursive: true });
|
|
16835
|
+
writeFileSync2(linkFile(dir), `${JSON.stringify(link, null, 2)}
|
|
16836
|
+
`);
|
|
16837
|
+
writeFileSync2(
|
|
16838
|
+
join13(linkDir(dir), "README.txt"),
|
|
16839
|
+
"This directory links the project to Naro and should not be committed.\n"
|
|
16840
|
+
);
|
|
16841
|
+
}
|
|
16842
|
+
function removeProjectLink(dir) {
|
|
16843
|
+
if (!existsSync4(linkDir(dir))) return false;
|
|
16844
|
+
rmSync2(linkDir(dir), { recursive: true, force: true });
|
|
16845
|
+
return true;
|
|
16846
|
+
}
|
|
16847
|
+
function ensureGitignoreHasNaro(dir) {
|
|
16848
|
+
const gitignorePath = join13(dir, ".gitignore");
|
|
16849
|
+
const hasGitignore = existsSync4(gitignorePath);
|
|
16850
|
+
if (!hasGitignore && !existsSync4(join13(dir, ".git"))) return false;
|
|
16851
|
+
const current = hasGitignore ? readFileSync5(gitignorePath, "utf8") : "";
|
|
16852
|
+
const covered = current.split(/\r?\n/).map((line) => line.trim()).some((line) => line === ".naro" || line === ".naro/" || line === "/.naro");
|
|
16853
|
+
if (covered) return false;
|
|
16854
|
+
const suffix = current.length === 0 || current.endsWith("\n") ? "" : "\n";
|
|
16855
|
+
writeFileSync2(gitignorePath, `${current}${suffix}.naro
|
|
16856
|
+
`);
|
|
16857
|
+
return true;
|
|
16858
|
+
}
|
|
16859
|
+
|
|
16860
|
+
// src/env-consent.ts
|
|
16861
|
+
import { existsSync as existsSync5 } from "fs";
|
|
16862
|
+
import { join as join14 } from "path";
|
|
16863
|
+
var DOTENV_FILES = [".env.local", ".env.production", ".env"];
|
|
16864
|
+
var PUBLIC_PREFIXES = ["NEXT_PUBLIC_", "VITE_", "REACT_APP_", "PUBLIC_"];
|
|
16865
|
+
function maskEnvValue(key, value) {
|
|
16866
|
+
if (PUBLIC_PREFIXES.some((prefix) => key.startsWith(prefix)))
|
|
16867
|
+
return value.length > 60 ? `${value.slice(0, 60)}\u2026` : value;
|
|
16868
|
+
return value.length <= 8 ? "****" : `${value.slice(0, 4)}****`;
|
|
16869
|
+
}
|
|
16870
|
+
function findDotenvFile(dir) {
|
|
16871
|
+
for (const name of DOTENV_FILES) if (existsSync5(join14(dir, name))) return name;
|
|
16872
|
+
return null;
|
|
16873
|
+
}
|
|
16874
|
+
function envAddGuidance(keys) {
|
|
16875
|
+
return [
|
|
16876
|
+
" Set them yourself:",
|
|
16877
|
+
...keys.map((key) => ` npx naro-cli env add ${key}`),
|
|
16878
|
+
" (values come from a hidden prompt or stdin, never from argv)"
|
|
16879
|
+
];
|
|
16880
|
+
}
|
|
16881
|
+
async function promptForMissingEnv(ctx, keys) {
|
|
16882
|
+
const guide = (remaining2) => {
|
|
16883
|
+
for (const line of envAddGuidance(remaining2)) ctx.output.log(line);
|
|
16884
|
+
};
|
|
16885
|
+
const file2 = findDotenvFile(ctx.cwd);
|
|
16886
|
+
if (!file2) {
|
|
16887
|
+
guide(keys);
|
|
16888
|
+
return {};
|
|
16889
|
+
}
|
|
16890
|
+
const consented = await ctx.prompter.confirm(
|
|
16891
|
+
`Found ${file2}. Read it to fill these ${String(keys.length)} value(s)?`
|
|
16892
|
+
);
|
|
16893
|
+
if (!consented) {
|
|
16894
|
+
ctx.output.log(`\u25B8 skipped reading ${file2}`);
|
|
16895
|
+
guide(keys);
|
|
16896
|
+
return {};
|
|
16897
|
+
}
|
|
16898
|
+
const local = (ctx.readEnv ?? readProjectEnv)(ctx.cwd);
|
|
16899
|
+
const collected = {};
|
|
16900
|
+
for (const key of keys) {
|
|
16901
|
+
const value = local[key];
|
|
16902
|
+
if (value !== void 0 && value !== "") collected[key] = value;
|
|
16903
|
+
}
|
|
16904
|
+
const names = Object.keys(collected);
|
|
16905
|
+
if (names.length === 0) {
|
|
16906
|
+
ctx.output.log(`\u25B8 no matching values in ${file2}`);
|
|
16907
|
+
guide(keys);
|
|
16908
|
+
return {};
|
|
16909
|
+
}
|
|
16910
|
+
const width = Math.max(...names.map((name) => name.length));
|
|
16911
|
+
for (const key of names)
|
|
16912
|
+
ctx.output.log(
|
|
16913
|
+
` ${key.padEnd(width)} ${maskEnvValue(key, collected[key] ?? "")}`
|
|
16914
|
+
);
|
|
16915
|
+
ctx.output.log(`\u25B8 adding ${String(names.length)} env var(s)`);
|
|
16916
|
+
const remaining = keys.filter((key) => !(key in collected));
|
|
16917
|
+
if (remaining.length > 0) guide(remaining);
|
|
16918
|
+
return collected;
|
|
16919
|
+
}
|
|
16920
|
+
|
|
16921
|
+
// src/git-info.ts
|
|
16922
|
+
import { execFile } from "child_process";
|
|
16923
|
+
var SUPPORTED_REMOTE = [
|
|
16924
|
+
/^https:\/\/[^\s]+$/,
|
|
16925
|
+
/^ssh:\/\/[^\s]+$/,
|
|
16926
|
+
/^git@[A-Za-z0-9.-]+:[A-Za-z0-9._/-]+(\.git)?$/
|
|
16927
|
+
];
|
|
16928
|
+
var defaultExec = (args, cwd) => new Promise((resolve4, reject) => {
|
|
16929
|
+
execFile("git", args, { cwd }, (error51, stdout) => {
|
|
16930
|
+
if (error51) reject(error51);
|
|
16931
|
+
else resolve4(stdout);
|
|
16932
|
+
});
|
|
16933
|
+
});
|
|
16934
|
+
async function tryExec(exec, args, cwd) {
|
|
16935
|
+
try {
|
|
16936
|
+
const out = (await exec(args, cwd)).trim();
|
|
16937
|
+
return out.length > 0 ? out : null;
|
|
16938
|
+
} catch {
|
|
16939
|
+
return null;
|
|
16940
|
+
}
|
|
16941
|
+
}
|
|
16942
|
+
async function detectGitInfo(dir, exec = defaultExec) {
|
|
16943
|
+
const remote = await tryExec(exec, ["remote", "get-url", "origin"], dir);
|
|
16944
|
+
const branch = await tryExec(
|
|
16945
|
+
exec,
|
|
16946
|
+
["rev-parse", "--abbrev-ref", "HEAD"],
|
|
16947
|
+
dir
|
|
16948
|
+
);
|
|
16949
|
+
return {
|
|
16950
|
+
repositoryUrl: remote !== null && SUPPORTED_REMOTE.some((re) => re.test(remote)) ? remote : null,
|
|
16951
|
+
branch: branch !== null && branch !== "HEAD" ? branch : null
|
|
16952
|
+
};
|
|
15840
16953
|
}
|
|
15841
16954
|
|
|
15842
16955
|
// src/upload.ts
|
|
@@ -15854,12 +16967,52 @@ async function authed(opts, path, init) {
|
|
|
15854
16967
|
);
|
|
15855
16968
|
return response;
|
|
15856
16969
|
}
|
|
15857
|
-
|
|
15858
|
-
|
|
15859
|
-
|
|
15860
|
-
|
|
15861
|
-
|
|
15862
|
-
|
|
16970
|
+
function uploadPorts(opts) {
|
|
16971
|
+
const doFetch = opts.fetch ?? fetch;
|
|
16972
|
+
return {
|
|
16973
|
+
resolvePutUrls: async (entries) => {
|
|
16974
|
+
try {
|
|
16975
|
+
const response = await authed(opts, "/artifacts/urls", {
|
|
16976
|
+
method: "POST",
|
|
16977
|
+
headers: { "Content-Type": "application/json" },
|
|
16978
|
+
body: JSON.stringify({
|
|
16979
|
+
files: entries.map((entry) => ({
|
|
16980
|
+
kind: entry.kind,
|
|
16981
|
+
name: entry.file.name
|
|
16982
|
+
}))
|
|
16983
|
+
})
|
|
16984
|
+
});
|
|
16985
|
+
return parseArtifactPutUrls(await response.json());
|
|
16986
|
+
} catch {
|
|
16987
|
+
return null;
|
|
16988
|
+
}
|
|
16989
|
+
},
|
|
16990
|
+
putSigned: async (url2, entry) => {
|
|
16991
|
+
const response = await doFetch(url2, {
|
|
16992
|
+
method: "PUT",
|
|
16993
|
+
headers: { "Content-Type": entry.file.contentType },
|
|
16994
|
+
body: entry.file.data
|
|
16995
|
+
});
|
|
16996
|
+
if (!response.ok)
|
|
16997
|
+
throw new Error(
|
|
16998
|
+
`Artifact upload returned ${String(response.status)} for ${entry.file.name}`
|
|
16999
|
+
);
|
|
17000
|
+
},
|
|
17001
|
+
putProxied: async (entry) => {
|
|
17002
|
+
await authed(
|
|
17003
|
+
opts,
|
|
17004
|
+
`/artifacts/${entry.kind}/${encodeURIComponent(entry.file.name)}`,
|
|
17005
|
+
{
|
|
17006
|
+
method: "PUT",
|
|
17007
|
+
headers: {
|
|
17008
|
+
"Content-Type": entry.file.contentType,
|
|
17009
|
+
"X-Artifact-Name": entry.file.name
|
|
17010
|
+
},
|
|
17011
|
+
body: entry.file.data
|
|
17012
|
+
}
|
|
17013
|
+
);
|
|
17014
|
+
}
|
|
17015
|
+
};
|
|
15863
17016
|
}
|
|
15864
17017
|
function manifest(artifact) {
|
|
15865
17018
|
const meta3 = ({ name, contentType: contentType2, data }) => ({
|
|
@@ -15878,13 +17031,12 @@ function manifest(artifact) {
|
|
|
15878
17031
|
})),
|
|
15879
17032
|
compatibilityDate: artifact.compatibilityDate,
|
|
15880
17033
|
compatibilityFlags: artifact.compatibilityFlags,
|
|
15881
|
-
assetsBinding: artifact.assetsBinding
|
|
17034
|
+
assetsBinding: artifact.assetsBinding,
|
|
17035
|
+
...artifact.assetsConfig ? { assetsConfig: artifact.assetsConfig } : {}
|
|
15882
17036
|
};
|
|
15883
17037
|
}
|
|
15884
17038
|
async function uploadArtifact(artifact, opts) {
|
|
15885
|
-
|
|
15886
|
-
await put(opts, "module", file2);
|
|
15887
|
-
for (const file2 of artifact.assets) await put(opts, "asset", file2);
|
|
17039
|
+
await uploadArtifactFiles(artifact, uploadPorts(opts));
|
|
15888
17040
|
const response = await authed(opts, "/artifacts/complete", {
|
|
15889
17041
|
method: "POST",
|
|
15890
17042
|
headers: { "Content-Type": "application/json" },
|
|
@@ -15900,63 +17052,11 @@ async function uploadArtifact(artifact, opts) {
|
|
|
15900
17052
|
}
|
|
15901
17053
|
|
|
15902
17054
|
// src/deploy.ts
|
|
15903
|
-
|
|
15904
|
-
|
|
15905
|
-
const response = await doFetch(
|
|
15906
|
-
`${opts.controlPlaneUrl.replace(/\/$/, "")}/api/cli/deployments`,
|
|
15907
|
-
{
|
|
15908
|
-
method: "POST",
|
|
15909
|
-
headers: {
|
|
15910
|
-
"Content-Type": "application/json",
|
|
15911
|
-
Authorization: `Bearer ${accessToken}`
|
|
15912
|
-
},
|
|
15913
|
-
body: JSON.stringify({
|
|
15914
|
-
name: opts.name,
|
|
15915
|
-
production: opts.production,
|
|
15916
|
-
...opts.repositoryUrl != null && {
|
|
15917
|
-
repositoryUrl: opts.repositoryUrl
|
|
15918
|
-
},
|
|
15919
|
-
...opts.branch != null && { branch: opts.branch },
|
|
15920
|
-
...opts.rootDirectory != null && {
|
|
15921
|
-
rootDirectory: opts.rootDirectory
|
|
15922
|
-
},
|
|
15923
|
-
...opts.env !== void 0 && { env: opts.env }
|
|
15924
|
-
})
|
|
15925
|
-
}
|
|
15926
|
-
);
|
|
15927
|
-
if (!response.ok) {
|
|
15928
|
-
if (response.status === 401)
|
|
15929
|
-
throw new Error("Session expired or invalid. Run `naro login`.");
|
|
15930
|
-
throw new Error(
|
|
15931
|
-
`Could not start deployment: control plane returned ${String(response.status)}.`
|
|
15932
|
-
);
|
|
15933
|
-
}
|
|
15934
|
-
return await response.json();
|
|
17055
|
+
function parseDotenv2(text) {
|
|
17056
|
+
return parseDotenv(text);
|
|
15935
17057
|
}
|
|
15936
|
-
function
|
|
15937
|
-
return (
|
|
15938
|
-
}
|
|
15939
|
-
async function requireSessionOrLogin(log2, home = homedir3(), loginFlow = () => login({
|
|
15940
|
-
openBrowser: (url2) => {
|
|
15941
|
-
log2(`If your browser did not open, visit:
|
|
15942
|
-
${url2}`);
|
|
15943
|
-
openBrowser(url2);
|
|
15944
|
-
}
|
|
15945
|
-
})) {
|
|
15946
|
-
const stored = loadStoredSession(home);
|
|
15947
|
-
if (stored) {
|
|
15948
|
-
if (!isExpired(stored)) return stored;
|
|
15949
|
-
try {
|
|
15950
|
-
const refreshed = await refreshStoredSession(stored.refresh_token);
|
|
15951
|
-
saveSession(refreshed, home);
|
|
15952
|
-
return refreshed;
|
|
15953
|
-
} catch {
|
|
15954
|
-
}
|
|
15955
|
-
}
|
|
15956
|
-
log2("Not logged in \u2014 opening your browser to sign in with GitHub\u2026");
|
|
15957
|
-
const session = await loginFlow();
|
|
15958
|
-
saveSession(session, home);
|
|
15959
|
-
return session;
|
|
17058
|
+
function readProjectEnv2(dir) {
|
|
17059
|
+
return readProjectEnv(dir);
|
|
15960
17060
|
}
|
|
15961
17061
|
function eventsObserver(cp2, deploymentId, token, fetchImpl = fetch) {
|
|
15962
17062
|
const post = (body) => fetchImpl(`${cp2}/api/internal/deployments/${deploymentId}/events`, {
|
|
@@ -15968,36 +17068,28 @@ function eventsObserver(cp2, deploymentId, token, fetchImpl = fetch) {
|
|
|
15968
17068
|
body: JSON.stringify(body)
|
|
15969
17069
|
}).then(() => {
|
|
15970
17070
|
});
|
|
17071
|
+
const postStatus = async (body) => {
|
|
17072
|
+
let lastError;
|
|
17073
|
+
for (let attempt = 0; attempt < 3; attempt++) {
|
|
17074
|
+
try {
|
|
17075
|
+
return await post(body);
|
|
17076
|
+
} catch (error51) {
|
|
17077
|
+
lastError = error51;
|
|
17078
|
+
await new Promise(
|
|
17079
|
+
(resolve4) => setTimeout(resolve4, 500 * (attempt + 1))
|
|
17080
|
+
);
|
|
17081
|
+
}
|
|
17082
|
+
}
|
|
17083
|
+
throw lastError;
|
|
17084
|
+
};
|
|
15971
17085
|
return {
|
|
15972
|
-
onStatus: (status) =>
|
|
17086
|
+
onStatus: (status) => postStatus({ type: "status", status }),
|
|
15973
17087
|
onLog: (log2) => post({ type: "log", log: log2 }).catch(() => {
|
|
15974
17088
|
}),
|
|
15975
17089
|
onMetadata: (metadata) => post({ type: "metadata", metadata }).catch(() => {
|
|
15976
17090
|
})
|
|
15977
17091
|
};
|
|
15978
17092
|
}
|
|
15979
|
-
function parseDotenv(text) {
|
|
15980
|
-
const out = {};
|
|
15981
|
-
for (const line of text.split(/\r?\n/)) {
|
|
15982
|
-
const trimmed = line.trim();
|
|
15983
|
-
if (!trimmed || trimmed.startsWith("#")) continue;
|
|
15984
|
-
const eq = trimmed.indexOf("=");
|
|
15985
|
-
if (eq <= 0) continue;
|
|
15986
|
-
const key = trimmed.slice(0, eq).trim();
|
|
15987
|
-
let value = trimmed.slice(eq + 1).trim();
|
|
15988
|
-
if (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'"))
|
|
15989
|
-
value = value.slice(1, -1);
|
|
15990
|
-
out[key] = value;
|
|
15991
|
-
}
|
|
15992
|
-
return out;
|
|
15993
|
-
}
|
|
15994
|
-
function readProjectEnv(dir) {
|
|
15995
|
-
for (const name of [".env.local", ".env.production", ".env"]) {
|
|
15996
|
-
const path = join9(dir, name);
|
|
15997
|
-
if (existsSync(path)) return parseDotenv(readFileSync2(path, "utf8"));
|
|
15998
|
-
}
|
|
15999
|
-
return {};
|
|
16000
|
-
}
|
|
16001
17093
|
function slugify2(value) {
|
|
16002
17094
|
const slug = value.replace(/^@[^/]+\//, "").toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 54);
|
|
16003
17095
|
return slug || "app";
|
|
@@ -16005,19 +17097,19 @@ function slugify2(value) {
|
|
|
16005
17097
|
async function projectName(sourceDir) {
|
|
16006
17098
|
try {
|
|
16007
17099
|
const pkg = JSON.parse(
|
|
16008
|
-
await
|
|
17100
|
+
await readFile7(join15(sourceDir, "package.json"), "utf8")
|
|
16009
17101
|
);
|
|
16010
17102
|
if (pkg.name) return pkg.name;
|
|
16011
17103
|
} catch {
|
|
16012
17104
|
}
|
|
16013
17105
|
return basename(sourceDir);
|
|
16014
17106
|
}
|
|
16015
|
-
var COPY_SKIP = /(^|[\\/])(node_modules|\.next|\.open-next|\.git|\.wrangler|\.turbo)([\\/]|$)/;
|
|
17107
|
+
var COPY_SKIP = /(^|[\\/])(node_modules|\.next|\.open-next|\.git|\.wrangler|\.turbo|\.naro)([\\/]|$)/;
|
|
16016
17108
|
function resolveBin(name) {
|
|
16017
17109
|
let dir = dirname(fileURLToPath(import.meta.url));
|
|
16018
17110
|
for (; ; ) {
|
|
16019
|
-
const candidate =
|
|
16020
|
-
if (
|
|
17111
|
+
const candidate = join15(dir, "node_modules", ".bin", name);
|
|
17112
|
+
if (existsSync6(candidate)) return candidate;
|
|
16021
17113
|
const parent = dirname(dir);
|
|
16022
17114
|
if (parent === dir) return name;
|
|
16023
17115
|
dir = parent;
|
|
@@ -16041,7 +17133,7 @@ async function detectPackageManagerLenient(root) {
|
|
|
16041
17133
|
}
|
|
16042
17134
|
function localDependencies(sourceDir) {
|
|
16043
17135
|
return {
|
|
16044
|
-
createWorkspace: () => mkdtemp2(
|
|
17136
|
+
createWorkspace: () => mkdtemp2(join15(tmpdir2(), "naro-build-")),
|
|
16045
17137
|
removeWorkspace: (path) => rm2(path, { recursive: true, force: true }),
|
|
16046
17138
|
async runCommand(spec, observer) {
|
|
16047
17139
|
if (spec.command === "git" && spec.args[0] === "clone") {
|
|
@@ -16063,7 +17155,9 @@ function localDependencies(sourceDir) {
|
|
|
16063
17155
|
},
|
|
16064
17156
|
detectPackageManager: detectPackageManagerLenient,
|
|
16065
17157
|
resolveAppRoot,
|
|
16066
|
-
|
|
17158
|
+
detectFramework,
|
|
17159
|
+
readStaticArtifact,
|
|
17160
|
+
createPackageManagerConfig,
|
|
16067
17161
|
createPlatformBuildConfig,
|
|
16068
17162
|
readOpenNextArtifact,
|
|
16069
17163
|
openNextCommand: resolveBin("opennextjs-cloudflare"),
|
|
@@ -16071,62 +17165,171 @@ function localDependencies(sourceDir) {
|
|
|
16071
17165
|
};
|
|
16072
17166
|
}
|
|
16073
17167
|
async function deployDirectory(sourceDir, options, log2) {
|
|
16074
|
-
const
|
|
16075
|
-
const session = await requireSessionOrLogin(log2);
|
|
16076
|
-
const name = slugify2(options.name ?? await projectName(sourceDir));
|
|
17168
|
+
const cpUrl = controlPlaneUrl();
|
|
16077
17169
|
const production = options.production ?? false;
|
|
17170
|
+
const framework = detectFramework2(sourceDir);
|
|
17171
|
+
if (!framework.deployable && !options.force) {
|
|
17172
|
+
throw new NaroError(
|
|
17173
|
+
"FRAMEWORK_UNSUPPORTED",
|
|
17174
|
+
framework.framework === "unknown" ? "No supported framework detected here. Naro deploys Next.js, Vite, Create React App, and static sites \u2014 run this inside one of those projects." : `${framework.name} projects are not deployable on Naro yet.`,
|
|
17175
|
+
{ details: { framework: framework.framework } }
|
|
17176
|
+
);
|
|
17177
|
+
}
|
|
17178
|
+
log2(`\u25B8 detected ${framework.name}`);
|
|
17179
|
+
const auth = await resolveAuth({
|
|
17180
|
+
explicitToken: options.token,
|
|
17181
|
+
interactiveLogin: options.allowLogin === false ? false : {
|
|
17182
|
+
openBrowser,
|
|
17183
|
+
log: log2
|
|
17184
|
+
}
|
|
17185
|
+
});
|
|
17186
|
+
const client = new NaroClient({
|
|
17187
|
+
baseUrl: cpUrl,
|
|
17188
|
+
getAccessToken: () => auth.getAccessToken(),
|
|
17189
|
+
...options.fetch ? { fetch: options.fetch } : {}
|
|
17190
|
+
});
|
|
17191
|
+
const name = slugify2(options.name ?? await projectName(sourceDir));
|
|
16078
17192
|
const git = await detectGitInfo(sourceDir);
|
|
16079
17193
|
const branch = options.branch ?? git.branch;
|
|
16080
|
-
if (
|
|
17194
|
+
if (options.projectId) log2(`\u25B8 linked project ${options.projectId}`);
|
|
17195
|
+
else if (git.repositoryUrl) log2(`\u25B8 linking ${git.repositoryUrl}`);
|
|
16081
17196
|
if (options.env && Object.keys(options.env).length > 0)
|
|
16082
|
-
log2(`\u25B8 syncing ${Object.keys(options.env).length} env var(s)`);
|
|
17197
|
+
log2(`\u25B8 syncing ${String(Object.keys(options.env).length)} env var(s)`);
|
|
17198
|
+
const scanned = scanEnvReferences(sourceDir).map(
|
|
17199
|
+
(reference) => reference.key
|
|
17200
|
+
);
|
|
17201
|
+
let missing = [];
|
|
17202
|
+
if (scanned.length > 0) {
|
|
17203
|
+
let existing = /* @__PURE__ */ new Set();
|
|
17204
|
+
if (options.projectId) {
|
|
17205
|
+
try {
|
|
17206
|
+
const rows = await client.env.list(options.projectId);
|
|
17207
|
+
existing = new Set(rows.map((row) => row.key));
|
|
17208
|
+
} catch {
|
|
17209
|
+
}
|
|
17210
|
+
}
|
|
17211
|
+
const provided = options.env ?? {};
|
|
17212
|
+
missing = scanned.filter((key) => !existing.has(key) && !(key in provided));
|
|
17213
|
+
}
|
|
17214
|
+
let collected = {};
|
|
17215
|
+
if (missing.length > 0) {
|
|
17216
|
+
log2(
|
|
17217
|
+
`\u25B8 ${String(missing.length)} env var(s) needed, not set on this project: ${missing.join(", ")}`
|
|
17218
|
+
);
|
|
17219
|
+
if (options.onMissingEnv) collected = await options.onMissingEnv(missing);
|
|
17220
|
+
else for (const line of envAddGuidance(missing)) log2(line);
|
|
17221
|
+
}
|
|
17222
|
+
const missingEnvKeys = missing.filter((key) => !(key in collected));
|
|
17223
|
+
const envToSend = { ...options.env, ...collected };
|
|
16083
17224
|
log2("\u25B8 starting deployment");
|
|
16084
|
-
const ctx = await
|
|
16085
|
-
name,
|
|
17225
|
+
const ctx = await client.deployments.createCli({
|
|
17226
|
+
...options.projectId ? { projectId: options.projectId } : { name },
|
|
16086
17227
|
production,
|
|
16087
|
-
|
|
16088
|
-
|
|
16089
|
-
|
|
16090
|
-
|
|
16091
|
-
|
|
17228
|
+
...git.repositoryUrl != null && { repositoryUrl: git.repositoryUrl },
|
|
17229
|
+
...branch != null && { branch },
|
|
17230
|
+
...options.rootDirectory != null && {
|
|
17231
|
+
rootDirectory: options.rootDirectory
|
|
17232
|
+
},
|
|
17233
|
+
...Object.keys(envToSend).length > 0 && { env: envToSend }
|
|
16092
17234
|
});
|
|
16093
17235
|
if (ctx.envSkipped?.length)
|
|
16094
17236
|
log2(`\u26A0 skipped env: ${ctx.envSkipped.join(", ")}`);
|
|
16095
|
-
const executor = new LocalBuildExecutor(localDependencies(sourceDir));
|
|
16096
|
-
const observer = eventsObserver(
|
|
16097
|
-
|
|
16098
|
-
|
|
16099
|
-
|
|
16100
|
-
|
|
16101
|
-
repositoryUrl: sourceDir,
|
|
16102
|
-
branch: ctx.branch,
|
|
16103
|
-
rootDirectory: options.rootDirectory ?? ctx.rootDirectory,
|
|
16104
|
-
env: { ...ctx.env, ...options.env ?? {} }
|
|
16105
|
-
},
|
|
16106
|
-
observer
|
|
17237
|
+
const executor = options.executor ?? new LocalBuildExecutor(localDependencies(sourceDir));
|
|
17238
|
+
const observer = eventsObserver(
|
|
17239
|
+
cpUrl,
|
|
17240
|
+
ctx.deploymentId,
|
|
17241
|
+
ctx.callbackToken,
|
|
17242
|
+
options.fetch
|
|
16107
17243
|
);
|
|
16108
|
-
|
|
16109
|
-
|
|
17244
|
+
let artifact;
|
|
17245
|
+
try {
|
|
17246
|
+
const result = await executor.build(
|
|
17247
|
+
{
|
|
17248
|
+
deploymentId: ctx.deploymentId,
|
|
17249
|
+
projectId: ctx.projectId,
|
|
17250
|
+
repositoryUrl: sourceDir,
|
|
17251
|
+
branch: ctx.branch,
|
|
17252
|
+
rootDirectory: options.rootDirectory ?? ctx.rootDirectory,
|
|
17253
|
+
env: { ...ctx.env, ...envToSend }
|
|
17254
|
+
},
|
|
17255
|
+
observer
|
|
17256
|
+
);
|
|
17257
|
+
artifact = result.artifact;
|
|
17258
|
+
} catch (error51) {
|
|
17259
|
+
throw new NaroError(
|
|
17260
|
+
"BUILD_FAILED",
|
|
17261
|
+
error51 instanceof Error ? error51.message : "The local build failed.",
|
|
17262
|
+
{ details: { deploymentId: ctx.deploymentId }, cause: error51 }
|
|
17263
|
+
);
|
|
17264
|
+
}
|
|
17265
|
+
await uploadArtifact(artifact, {
|
|
17266
|
+
controlPlaneUrl: cpUrl,
|
|
16110
17267
|
deploymentId: ctx.deploymentId,
|
|
16111
|
-
callbackToken: ctx.callbackToken
|
|
17268
|
+
callbackToken: ctx.callbackToken,
|
|
17269
|
+
...options.fetch ? { fetch: options.fetch } : {}
|
|
16112
17270
|
});
|
|
16113
|
-
|
|
17271
|
+
log2("\u25B8 publishing worker");
|
|
17272
|
+
const detail = await waitForFinish(client, ctx, log2);
|
|
17273
|
+
const url2 = deploymentUrl(detail, ctx, production);
|
|
17274
|
+
if (detail.deployment.status === "FAILED") {
|
|
17275
|
+
throw new NaroError(
|
|
17276
|
+
detail.deployment.errorCode ?? "DEPLOY_FAILED",
|
|
17277
|
+
detail.deployment.errorMessage ?? "The deployment failed.",
|
|
17278
|
+
{ details: { deploymentId: ctx.deploymentId } }
|
|
17279
|
+
);
|
|
17280
|
+
}
|
|
16114
17281
|
return {
|
|
16115
|
-
|
|
16116
|
-
|
|
17282
|
+
deploymentId: ctx.deploymentId,
|
|
17283
|
+
projectId: ctx.projectId,
|
|
17284
|
+
status: detail.deployment.status,
|
|
17285
|
+
url: url2,
|
|
17286
|
+
production,
|
|
17287
|
+
missingEnvKeys
|
|
16117
17288
|
};
|
|
16118
17289
|
}
|
|
17290
|
+
async function waitForFinish(client, ctx, log2) {
|
|
17291
|
+
return client.deployments.wait(ctx.deploymentId, {
|
|
17292
|
+
intervalMs: 2500,
|
|
17293
|
+
timeoutMs: 10 * 6e4,
|
|
17294
|
+
onStatus: (status) => {
|
|
17295
|
+
if (status === "READY") log2("\u2713 worker deployed");
|
|
17296
|
+
}
|
|
17297
|
+
});
|
|
17298
|
+
}
|
|
17299
|
+
function deploymentUrl(detail, ctx, production) {
|
|
17300
|
+
if (detail.deployment.url) return detail.deployment.url;
|
|
17301
|
+
const surface = ctx.subdomain ?? ctx.projectId;
|
|
17302
|
+
const host = new URL(ctx.controlPlaneUrl || controlPlaneUrl()).hostname;
|
|
17303
|
+
return `https://${surface}${production ? "" : "-preview"}.${host}`;
|
|
17304
|
+
}
|
|
16119
17305
|
|
|
16120
17306
|
export {
|
|
16121
|
-
|
|
16122
|
-
|
|
16123
|
-
|
|
16124
|
-
|
|
17307
|
+
parseGitHubRemote,
|
|
17308
|
+
NaroError,
|
|
17309
|
+
isNaroError,
|
|
17310
|
+
toNaroError,
|
|
17311
|
+
saveSession,
|
|
17312
|
+
loadStoredSession,
|
|
17313
|
+
clearSession,
|
|
17314
|
+
isExpired,
|
|
16125
17315
|
controlPlaneUrl,
|
|
16126
|
-
|
|
17316
|
+
createAuthClient,
|
|
17317
|
+
refreshStoredSession,
|
|
17318
|
+
login,
|
|
17319
|
+
resolveAuth,
|
|
17320
|
+
openBrowser,
|
|
17321
|
+
isTerminalStatus,
|
|
17322
|
+
NaroClient,
|
|
17323
|
+
serializeDotenv,
|
|
17324
|
+
readProjectLink,
|
|
17325
|
+
writeProjectLink,
|
|
17326
|
+
removeProjectLink,
|
|
17327
|
+
ensureGitignoreHasNaro,
|
|
17328
|
+
promptForMissingEnv,
|
|
17329
|
+
detectGitInfo,
|
|
17330
|
+
parseDotenv2 as parseDotenv,
|
|
17331
|
+
readProjectEnv2 as readProjectEnv,
|
|
16127
17332
|
eventsObserver,
|
|
16128
|
-
parseDotenv,
|
|
16129
|
-
readProjectEnv,
|
|
16130
17333
|
slugify2 as slugify,
|
|
16131
17334
|
deployDirectory
|
|
16132
17335
|
};
|