naro-cli 0.2.0 → 0.3.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 +112 -4
- package/dist/artifact-types.d.ts +4 -0
- package/dist/{chunk-UNCVFCTH.js → chunk-B6F7QIW7.js} +1425 -395
- package/dist/deploy.d.ts +39 -25
- package/dist/deploy.js +4 -10
- package/dist/index.js +1235 -64
- package/package.json +3 -2
- 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 existsSync4 } 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 join13 } 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";
|
|
@@ -107,18 +158,31 @@ async function saveCache(args) {
|
|
|
107
158
|
await log(observer, scope, "INFO", `Saved ${scope} cache (${key}).`);
|
|
108
159
|
}
|
|
109
160
|
|
|
161
|
+
// ../../packages/build/src/hash.ts
|
|
162
|
+
import { createHash as createHash2 } from "crypto";
|
|
163
|
+
function hashAsset(data) {
|
|
164
|
+
return createHash2("sha256").update(data).digest("hex").slice(0, 32);
|
|
165
|
+
}
|
|
166
|
+
|
|
110
167
|
// ../../packages/build/src/local-build-executor.ts
|
|
111
168
|
import { mkdtemp, rm } from "fs/promises";
|
|
112
169
|
import { tmpdir } from "os";
|
|
113
|
-
import { join as
|
|
170
|
+
import { join as join8 } from "path";
|
|
171
|
+
|
|
172
|
+
// ../../packages/core/src/artifact-manifest.ts
|
|
173
|
+
var MAX_FILE_BYTES = 25 * 1024 * 1024;
|
|
174
|
+
var MAX_ARTIFACT_BYTES = 64 * 1024 * 1024;
|
|
114
175
|
|
|
115
176
|
// ../../packages/core/src/errors.ts
|
|
116
177
|
var DeploymentError = class extends Error {
|
|
117
178
|
code;
|
|
179
|
+
/** HTTP status from the upstream response that caused this error, when known. */
|
|
180
|
+
status;
|
|
118
181
|
constructor(code, message, options) {
|
|
119
182
|
super(message, options);
|
|
120
183
|
this.name = "DeploymentError";
|
|
121
184
|
this.code = code;
|
|
185
|
+
if (options?.status !== void 0) this.status = options.status;
|
|
122
186
|
}
|
|
123
187
|
};
|
|
124
188
|
|
|
@@ -14636,6 +14700,41 @@ function date4(params) {
|
|
|
14636
14700
|
// ../../node_modules/.pnpm/zod@4.4.3/node_modules/zod/v4/classic/external.js
|
|
14637
14701
|
config(en_default());
|
|
14638
14702
|
|
|
14703
|
+
// ../../packages/core/src/subdomain.ts
|
|
14704
|
+
var RESERVED_SUBDOMAINS = [
|
|
14705
|
+
"admin",
|
|
14706
|
+
"api",
|
|
14707
|
+
"app",
|
|
14708
|
+
"assets",
|
|
14709
|
+
"blog",
|
|
14710
|
+
"cdn",
|
|
14711
|
+
"dashboard",
|
|
14712
|
+
"docs",
|
|
14713
|
+
"help",
|
|
14714
|
+
"imap",
|
|
14715
|
+
"mail",
|
|
14716
|
+
"mx",
|
|
14717
|
+
"naro",
|
|
14718
|
+
"ns1",
|
|
14719
|
+
"ns2",
|
|
14720
|
+
"preview",
|
|
14721
|
+
"smtp",
|
|
14722
|
+
"static",
|
|
14723
|
+
"status",
|
|
14724
|
+
"support",
|
|
14725
|
+
"www"
|
|
14726
|
+
];
|
|
14727
|
+
var SUBDOMAIN_PATTERN = /^[a-z0-9](-?[a-z0-9])*$/;
|
|
14728
|
+
var SUBDOMAIN_MAX_LENGTH = 54;
|
|
14729
|
+
function subdomainProblem(value) {
|
|
14730
|
+
if (value.length < 1 || value.length > SUBDOMAIN_MAX_LENGTH) return "invalid";
|
|
14731
|
+
if (!SUBDOMAIN_PATTERN.test(value)) return "invalid";
|
|
14732
|
+
if (value.endsWith("-preview")) return "reserved";
|
|
14733
|
+
if (RESERVED_SUBDOMAINS.includes(value))
|
|
14734
|
+
return "reserved";
|
|
14735
|
+
return null;
|
|
14736
|
+
}
|
|
14737
|
+
|
|
14639
14738
|
// ../../packages/core/src/schemas.ts
|
|
14640
14739
|
function isSupportedGitUrl(value) {
|
|
14641
14740
|
if (/^git@[A-Za-z0-9.-]+:[A-Za-z0-9._/-]+(?:\.git)?$/.test(value)) {
|
|
@@ -14666,12 +14765,30 @@ var createDeploymentSchema = external_exports.object({
|
|
|
14666
14765
|
branch: external_exports.string().trim().max(240).optional().transform((value) => value || null)
|
|
14667
14766
|
});
|
|
14668
14767
|
var connectRepositorySchema = external_exports.object({
|
|
14768
|
+
// Web-only route context: the only consumer is apps/web's
|
|
14769
|
+
// POST /api/projects handler, which resolves this to a team id.
|
|
14770
|
+
teamSlug: external_exports.string().trim().min(1),
|
|
14669
14771
|
name: external_exports.string().trim().min(1).max(80),
|
|
14670
14772
|
installationId: external_exports.number().int().positive(),
|
|
14671
14773
|
repoId: external_exports.number().int().positive(),
|
|
14672
14774
|
fullName: external_exports.string().trim().min(1).max(200).regex(/^[^/]+\/[^/]+$/, { message: "fullName must be owner/name" }),
|
|
14673
14775
|
defaultBranch: external_exports.string().trim().min(1).max(240).default("main"),
|
|
14674
|
-
rootDirectory: optionalRootDirectory
|
|
14776
|
+
rootDirectory: optionalRootDirectory,
|
|
14777
|
+
subdomain: external_exports.string().trim().max(60).optional().transform((value) => value ? value.toLowerCase() : null).superRefine((value, ctx) => {
|
|
14778
|
+
if (value === null) return;
|
|
14779
|
+
const problem = subdomainProblem(value);
|
|
14780
|
+
if (problem === "invalid") {
|
|
14781
|
+
ctx.addIssue({
|
|
14782
|
+
code: external_exports.ZodIssueCode.custom,
|
|
14783
|
+
message: "Use lowercase letters, numbers, and single hyphens (up to 54 characters)."
|
|
14784
|
+
});
|
|
14785
|
+
} else if (problem === "reserved") {
|
|
14786
|
+
ctx.addIssue({
|
|
14787
|
+
code: external_exports.ZodIssueCode.custom,
|
|
14788
|
+
message: "This subdomain is reserved."
|
|
14789
|
+
});
|
|
14790
|
+
}
|
|
14791
|
+
})
|
|
14675
14792
|
});
|
|
14676
14793
|
|
|
14677
14794
|
// ../../packages/build/src/package-manager.ts
|
|
@@ -14777,6 +14894,16 @@ async function pathExists(path) {
|
|
|
14777
14894
|
return false;
|
|
14778
14895
|
}
|
|
14779
14896
|
}
|
|
14897
|
+
async function createPackageManagerConfig(appRoot, writer = async (path, contents) => {
|
|
14898
|
+
await writeFile3(path, contents, "utf8");
|
|
14899
|
+
}) {
|
|
14900
|
+
const packageManagerConfigPath = join4(appRoot, ".next-deploy-user.npmrc");
|
|
14901
|
+
await writer(
|
|
14902
|
+
packageManagerConfigPath,
|
|
14903
|
+
"# Host package-manager credentials are intentionally unavailable.\n"
|
|
14904
|
+
);
|
|
14905
|
+
return packageManagerConfigPath;
|
|
14906
|
+
}
|
|
14780
14907
|
async function createPlatformBuildConfig(appRoot, deploymentId, writer = async (path, contents) => {
|
|
14781
14908
|
await writeFile3(path, contents, "utf8");
|
|
14782
14909
|
}, cloudflareModulePath = createRequire(import.meta.url).resolve(
|
|
@@ -14784,7 +14911,6 @@ async function createPlatformBuildConfig(appRoot, deploymentId, writer = async (
|
|
|
14784
14911
|
), exists3 = pathExists) {
|
|
14785
14912
|
const openNextConfigPath = join4(appRoot, ".next-deploy-open-next.config.mjs");
|
|
14786
14913
|
const wranglerConfigPath = join4(appRoot, ".next-deploy-wrangler.json");
|
|
14787
|
-
const packageManagerConfigPath = join4(appRoot, ".next-deploy-user.npmrc");
|
|
14788
14914
|
const safeDeploymentId = deploymentId.replace(/[^a-z0-9-]/gi, "-");
|
|
14789
14915
|
let hasNextConfig = false;
|
|
14790
14916
|
for (const name of NEXT_CONFIG_FILES) {
|
|
@@ -14824,10 +14950,7 @@ export default defineCloudflareConfig({});
|
|
|
14824
14950
|
)}
|
|
14825
14951
|
`
|
|
14826
14952
|
);
|
|
14827
|
-
await writer
|
|
14828
|
-
packageManagerConfigPath,
|
|
14829
|
-
"# Host package-manager credentials are intentionally unavailable.\n"
|
|
14830
|
-
);
|
|
14953
|
+
const packageManagerConfigPath = await createPackageManagerConfig(appRoot, writer);
|
|
14831
14954
|
return {
|
|
14832
14955
|
openNextConfigPath,
|
|
14833
14956
|
wranglerConfigPath,
|
|
@@ -14835,9 +14958,129 @@ export default defineCloudflareConfig({});
|
|
|
14835
14958
|
};
|
|
14836
14959
|
}
|
|
14837
14960
|
|
|
14961
|
+
// ../../packages/build/src/frameworks.ts
|
|
14962
|
+
import { readFile as readFile4 } from "fs/promises";
|
|
14963
|
+
import { join as join5 } from "path";
|
|
14964
|
+
var FRAMEWORK_PRESETS = {
|
|
14965
|
+
next: {
|
|
14966
|
+
id: "next",
|
|
14967
|
+
detectDependency: "next",
|
|
14968
|
+
defaultBuildScript: "build",
|
|
14969
|
+
defaultOutputDirectory: ".open-next",
|
|
14970
|
+
needsServerRuntime: true,
|
|
14971
|
+
spaFallback: false,
|
|
14972
|
+
cachePath: ".next/cache"
|
|
14973
|
+
},
|
|
14974
|
+
vite: {
|
|
14975
|
+
id: "vite",
|
|
14976
|
+
detectDependency: "vite",
|
|
14977
|
+
defaultBuildScript: "build",
|
|
14978
|
+
defaultOutputDirectory: "dist",
|
|
14979
|
+
needsServerRuntime: false,
|
|
14980
|
+
spaFallback: true,
|
|
14981
|
+
cachePath: "node_modules/.vite"
|
|
14982
|
+
},
|
|
14983
|
+
cra: {
|
|
14984
|
+
id: "cra",
|
|
14985
|
+
detectDependency: "react-scripts",
|
|
14986
|
+
defaultBuildScript: "build",
|
|
14987
|
+
defaultOutputDirectory: "build",
|
|
14988
|
+
needsServerRuntime: false,
|
|
14989
|
+
spaFallback: true,
|
|
14990
|
+
cachePath: "node_modules/.cache"
|
|
14991
|
+
},
|
|
14992
|
+
static: {
|
|
14993
|
+
id: "static",
|
|
14994
|
+
detectDependency: null,
|
|
14995
|
+
defaultBuildScript: null,
|
|
14996
|
+
defaultOutputDirectory: ".",
|
|
14997
|
+
needsServerRuntime: false,
|
|
14998
|
+
spaFallback: false,
|
|
14999
|
+
cachePath: null
|
|
15000
|
+
}
|
|
15001
|
+
};
|
|
15002
|
+
var DETECTION_ORDER = ["next", "vite", "cra"];
|
|
15003
|
+
var FRAMEWORK_LABELS = {
|
|
15004
|
+
next: "Next.js",
|
|
15005
|
+
vite: "Vite",
|
|
15006
|
+
cra: "Create React App",
|
|
15007
|
+
static: "static files"
|
|
15008
|
+
};
|
|
15009
|
+
function dependencyVersion(packageJson, name) {
|
|
15010
|
+
const version2 = packageJson.dependencies?.[name] ?? packageJson.devDependencies?.[name];
|
|
15011
|
+
return typeof version2 === "string" && version2.trim() !== "" ? version2 : null;
|
|
15012
|
+
}
|
|
15013
|
+
async function readPackageJson(root) {
|
|
15014
|
+
let source;
|
|
15015
|
+
try {
|
|
15016
|
+
source = await readFile4(join5(root, "package.json"), "utf8");
|
|
15017
|
+
} catch (error51) {
|
|
15018
|
+
if (error51.code === "ENOENT") return null;
|
|
15019
|
+
throw new DeploymentError(
|
|
15020
|
+
"FRAMEWORK_DETECTION_FAILED",
|
|
15021
|
+
"Could not read package.json.",
|
|
15022
|
+
{ cause: error51 }
|
|
15023
|
+
);
|
|
15024
|
+
}
|
|
15025
|
+
try {
|
|
15026
|
+
return JSON.parse(source);
|
|
15027
|
+
} catch (error51) {
|
|
15028
|
+
throw new DeploymentError(
|
|
15029
|
+
"FRAMEWORK_DETECTION_FAILED",
|
|
15030
|
+
"The selected root does not contain a valid package.json.",
|
|
15031
|
+
{ cause: error51 }
|
|
15032
|
+
);
|
|
15033
|
+
}
|
|
15034
|
+
}
|
|
15035
|
+
async function detectFramework(root, overrides = {}) {
|
|
15036
|
+
const packageJson = await readPackageJson(root);
|
|
15037
|
+
let preset;
|
|
15038
|
+
if (overrides.framework) {
|
|
15039
|
+
const candidate = FRAMEWORK_PRESETS[overrides.framework] ?? null;
|
|
15040
|
+
if (!candidate || candidate.id !== overrides.framework)
|
|
15041
|
+
throw new DeploymentError(
|
|
15042
|
+
"FRAMEWORK_DETECTION_FAILED",
|
|
15043
|
+
`Unknown framework "${overrides.framework}".`
|
|
15044
|
+
);
|
|
15045
|
+
preset = candidate;
|
|
15046
|
+
} else if (!packageJson) {
|
|
15047
|
+
preset = FRAMEWORK_PRESETS.static;
|
|
15048
|
+
} else {
|
|
15049
|
+
const detected = DETECTION_ORDER.map((id) => FRAMEWORK_PRESETS[id]).find(
|
|
15050
|
+
(candidate) => candidate.detectDependency !== null && dependencyVersion(packageJson, candidate.detectDependency) !== null
|
|
15051
|
+
);
|
|
15052
|
+
if (!detected)
|
|
15053
|
+
throw new DeploymentError(
|
|
15054
|
+
"FRAMEWORK_DETECTION_FAILED",
|
|
15055
|
+
"Could not detect the project framework from package.json. Set a framework (and build command / output directory) in the project settings."
|
|
15056
|
+
);
|
|
15057
|
+
preset = detected;
|
|
15058
|
+
}
|
|
15059
|
+
const buildScript = overrides.buildCommand ?? preset.defaultBuildScript;
|
|
15060
|
+
if (buildScript !== null) {
|
|
15061
|
+
if (!packageJson)
|
|
15062
|
+
throw new DeploymentError(
|
|
15063
|
+
"FRAMEWORK_DETECTION_FAILED",
|
|
15064
|
+
"A build command requires a package.json."
|
|
15065
|
+
);
|
|
15066
|
+
if (typeof packageJson.scripts?.[buildScript] !== "string")
|
|
15067
|
+
throw new DeploymentError(
|
|
15068
|
+
"BUILD_FAILED",
|
|
15069
|
+
`The project must declare a "${buildScript}" script.`
|
|
15070
|
+
);
|
|
15071
|
+
}
|
|
15072
|
+
const version2 = packageJson && preset.detectDependency ? dependencyVersion(packageJson, preset.detectDependency) : null;
|
|
15073
|
+
return {
|
|
15074
|
+
preset,
|
|
15075
|
+
buildScript,
|
|
15076
|
+
outputDirectory: overrides.outputDirectory ?? preset.defaultOutputDirectory,
|
|
15077
|
+
description: version2 ? `${FRAMEWORK_LABELS[preset.id]} ${version2}` : FRAMEWORK_LABELS[preset.id]
|
|
15078
|
+
};
|
|
15079
|
+
}
|
|
15080
|
+
|
|
14838
15081
|
// ../../packages/build/src/open-next.ts
|
|
14839
|
-
import { access as access4, readdir, readFile as
|
|
14840
|
-
import { extname, isAbsolute, join as
|
|
15082
|
+
import { access as access4, readdir, readFile as readFile5 } from "fs/promises";
|
|
15083
|
+
import { extname, isAbsolute, join as join6, relative, resolve, sep } from "path";
|
|
14841
15084
|
function parseJsonc(source) {
|
|
14842
15085
|
let output = "";
|
|
14843
15086
|
let index = 0;
|
|
@@ -14932,7 +15175,7 @@ async function listFiles(root) {
|
|
|
14932
15175
|
for (const entry of entries.sort(
|
|
14933
15176
|
(left, right) => left.name.localeCompare(right.name)
|
|
14934
15177
|
)) {
|
|
14935
|
-
const path =
|
|
15178
|
+
const path = join6(directory, entry.name);
|
|
14936
15179
|
if (entry.isSymbolicLink()) {
|
|
14937
15180
|
throw new DeploymentError(
|
|
14938
15181
|
"ARTIFACT_INVALID",
|
|
@@ -14943,7 +15186,7 @@ async function listFiles(root) {
|
|
|
14943
15186
|
else if (entry.isFile()) {
|
|
14944
15187
|
files.push({
|
|
14945
15188
|
name: relative(root, path).split(sep).join("/"),
|
|
14946
|
-
data: await
|
|
15189
|
+
data: await readFile5(path),
|
|
14947
15190
|
contentType: contentType(path)
|
|
14948
15191
|
});
|
|
14949
15192
|
}
|
|
@@ -14966,7 +15209,7 @@ async function findWranglerConfig(root, configuredPath) {
|
|
|
14966
15209
|
return resolved;
|
|
14967
15210
|
}
|
|
14968
15211
|
for (const name of ["wrangler.jsonc", "wrangler.json"]) {
|
|
14969
|
-
const path =
|
|
15212
|
+
const path = join6(root, name);
|
|
14970
15213
|
try {
|
|
14971
15214
|
await access4(path);
|
|
14972
15215
|
return path;
|
|
@@ -14982,7 +15225,7 @@ async function readOpenNextArtifact(appRoot, identity, wranglerConfigPath) {
|
|
|
14982
15225
|
try {
|
|
14983
15226
|
const configPath = await findWranglerConfig(appRoot, wranglerConfigPath);
|
|
14984
15227
|
const metadata = parseJsonc(
|
|
14985
|
-
await
|
|
15228
|
+
await readFile5(configPath, "utf8")
|
|
14986
15229
|
);
|
|
14987
15230
|
if (typeof metadata.main !== "string" || typeof metadata.compatibility_date !== "string") {
|
|
14988
15231
|
throw new DeploymentError(
|
|
@@ -15004,7 +15247,7 @@ async function readOpenNextArtifact(appRoot, identity, wranglerConfigPath) {
|
|
|
15004
15247
|
);
|
|
15005
15248
|
}
|
|
15006
15249
|
await access4(entrypointPath);
|
|
15007
|
-
const configuredAssets = typeof metadata.assets?.directory === "string" ? confinedPath(appRoot, metadata.assets.directory, "Assets directory") :
|
|
15250
|
+
const configuredAssets = typeof metadata.assets?.directory === "string" ? confinedPath(appRoot, metadata.assets.directory, "Assets directory") : join6(outputRoot, "assets");
|
|
15008
15251
|
const assetsRelation = relative(outputRoot, configuredAssets);
|
|
15009
15252
|
if (assetsRelation === ".." || assetsRelation.startsWith(`..${sep}`) || isAbsolute(assetsRelation)) {
|
|
15010
15253
|
throw new DeploymentError(
|
|
@@ -15012,7 +15255,7 @@ async function readOpenNextArtifact(appRoot, identity, wranglerConfigPath) {
|
|
|
15012
15255
|
"Assets directory must be inside .open-next."
|
|
15013
15256
|
);
|
|
15014
15257
|
}
|
|
15015
|
-
const bundleRoot =
|
|
15258
|
+
const bundleRoot = join6(outputRoot, "worker-bundle");
|
|
15016
15259
|
let bundleFiles = null;
|
|
15017
15260
|
try {
|
|
15018
15261
|
await access4(bundleRoot);
|
|
@@ -15197,48 +15440,99 @@ function resolveAppRoot(checkout, configuredRoot) {
|
|
|
15197
15440
|
return appRoot;
|
|
15198
15441
|
}
|
|
15199
15442
|
|
|
15200
|
-
// ../../packages/build/src/
|
|
15201
|
-
import { readFile as
|
|
15202
|
-
import { join as
|
|
15203
|
-
|
|
15204
|
-
|
|
15205
|
-
|
|
15206
|
-
|
|
15207
|
-
|
|
15443
|
+
// ../../packages/build/src/static-artifact.ts
|
|
15444
|
+
import { readdir as readdir2, readFile as readFile6 } from "fs/promises";
|
|
15445
|
+
import { isAbsolute as isAbsolute3, join as join7, relative as relative3, resolve as resolve3, sep as sep2 } from "path";
|
|
15446
|
+
var SHIM_WORKER_SOURCE = "export default { fetch: (request, env) => env.ASSETS.fetch(request) };\n";
|
|
15447
|
+
var EXCLUDED_DIRECTORIES = /* @__PURE__ */ new Set(["node_modules"]);
|
|
15448
|
+
var ALLOWED_DOTFILE = ".well-known";
|
|
15449
|
+
function excluded(name) {
|
|
15450
|
+
if (name === ALLOWED_DOTFILE) return false;
|
|
15451
|
+
return name.startsWith(".") || EXCLUDED_DIRECTORIES.has(name);
|
|
15452
|
+
}
|
|
15453
|
+
async function readStaticArtifact(appRoot, outputDirectory, identity, options, onWarn) {
|
|
15454
|
+
if (isAbsolute3(outputDirectory))
|
|
15455
|
+
throw new DeploymentError(
|
|
15456
|
+
"ARTIFACT_INVALID",
|
|
15457
|
+
"Output directory must be relative to the application root."
|
|
15208
15458
|
);
|
|
15209
|
-
|
|
15459
|
+
const outputRoot = resolve3(appRoot, outputDirectory);
|
|
15460
|
+
const relation = relative3(appRoot, outputRoot);
|
|
15461
|
+
if (relation === ".." || relation.startsWith(`..${sep2}`) || isAbsolute3(relation))
|
|
15210
15462
|
throw new DeploymentError(
|
|
15211
|
-
"
|
|
15212
|
-
"
|
|
15213
|
-
{
|
|
15214
|
-
cause: error51
|
|
15215
|
-
}
|
|
15463
|
+
"ARTIFACT_INVALID",
|
|
15464
|
+
"Output directory escapes the application root."
|
|
15216
15465
|
);
|
|
15466
|
+
const assets = [];
|
|
15467
|
+
async function visit(directory) {
|
|
15468
|
+
const entries = await readdir2(directory, { withFileTypes: true });
|
|
15469
|
+
for (const entry of entries.sort(
|
|
15470
|
+
(left, right) => left.name.localeCompare(right.name)
|
|
15471
|
+
)) {
|
|
15472
|
+
if (excluded(entry.name)) continue;
|
|
15473
|
+
const path = join7(directory, entry.name);
|
|
15474
|
+
if (entry.isSymbolicLink())
|
|
15475
|
+
throw new DeploymentError(
|
|
15476
|
+
"ARTIFACT_INVALID",
|
|
15477
|
+
"Static output cannot contain symbolic links."
|
|
15478
|
+
);
|
|
15479
|
+
if (entry.isDirectory()) await visit(path);
|
|
15480
|
+
else if (entry.isFile()) {
|
|
15481
|
+
assets.push({
|
|
15482
|
+
name: relative3(outputRoot, path).split(sep2).join("/"),
|
|
15483
|
+
data: await readFile6(path),
|
|
15484
|
+
contentType: contentType(path)
|
|
15485
|
+
});
|
|
15486
|
+
}
|
|
15487
|
+
}
|
|
15217
15488
|
}
|
|
15218
|
-
|
|
15219
|
-
|
|
15489
|
+
try {
|
|
15490
|
+
await visit(outputRoot);
|
|
15491
|
+
} catch (error51) {
|
|
15492
|
+
if (error51 instanceof DeploymentError) throw error51;
|
|
15220
15493
|
throw new DeploymentError(
|
|
15221
|
-
"
|
|
15222
|
-
|
|
15494
|
+
"ARTIFACT_INVALID",
|
|
15495
|
+
`Could not read the build output directory "${outputDirectory}".`,
|
|
15496
|
+
{ cause: error51 }
|
|
15223
15497
|
);
|
|
15224
15498
|
}
|
|
15225
|
-
if (
|
|
15499
|
+
if (assets.length === 0)
|
|
15226
15500
|
throw new DeploymentError(
|
|
15227
|
-
"
|
|
15228
|
-
|
|
15501
|
+
"ARTIFACT_INVALID",
|
|
15502
|
+
`The build output directory "${outputDirectory}" contains no files.`
|
|
15229
15503
|
);
|
|
15230
|
-
|
|
15231
|
-
|
|
15504
|
+
if (!assets.some((file2) => file2.name === "index.html"))
|
|
15505
|
+
await onWarn?.(
|
|
15506
|
+
`No index.html in "${outputDirectory}" \u2014 the site root will not serve a page.`
|
|
15507
|
+
);
|
|
15508
|
+
return {
|
|
15509
|
+
...identity,
|
|
15510
|
+
entrypoint: {
|
|
15511
|
+
name: "worker.js",
|
|
15512
|
+
data: new TextEncoder().encode(SHIM_WORKER_SOURCE),
|
|
15513
|
+
contentType: "application/javascript"
|
|
15514
|
+
},
|
|
15515
|
+
modules: [],
|
|
15516
|
+
assets,
|
|
15517
|
+
compatibilityDate: COMPATIBILITY_DATE,
|
|
15518
|
+
compatibilityFlags: [],
|
|
15519
|
+
assetsBinding: "ASSETS",
|
|
15520
|
+
assetsConfig: {
|
|
15521
|
+
notFoundHandling: options.spaFallback ? "single-page-application" : "404-page"
|
|
15522
|
+
}
|
|
15523
|
+
};
|
|
15232
15524
|
}
|
|
15233
15525
|
|
|
15234
15526
|
// ../../packages/build/src/local-build-executor.ts
|
|
15235
15527
|
var defaultDependencies = {
|
|
15236
|
-
createWorkspace: () => mkdtemp(
|
|
15528
|
+
createWorkspace: () => mkdtemp(join8(tmpdir(), "next-deploy-build-")),
|
|
15237
15529
|
removeWorkspace: (path) => rm(path, { recursive: true, force: true }),
|
|
15238
15530
|
runCommand,
|
|
15239
15531
|
detectPackageManager,
|
|
15240
15532
|
resolveAppRoot,
|
|
15241
|
-
|
|
15533
|
+
detectFramework,
|
|
15534
|
+
readStaticArtifact,
|
|
15535
|
+
createPackageManagerConfig,
|
|
15242
15536
|
createPlatformBuildConfig,
|
|
15243
15537
|
readOpenNextArtifact,
|
|
15244
15538
|
openNextCommand: "opennextjs-cloudflare",
|
|
@@ -15284,14 +15578,14 @@ var LocalBuildExecutor = class {
|
|
|
15284
15578
|
}
|
|
15285
15579
|
async build(input, observer, cache) {
|
|
15286
15580
|
const workspace = await this.dependencies.createWorkspace();
|
|
15287
|
-
const checkout =
|
|
15288
|
-
const pmCacheDir =
|
|
15581
|
+
const checkout = join8(workspace, "checkout");
|
|
15582
|
+
const pmCacheDir = join8(workspace, ".pm-store");
|
|
15289
15583
|
let cacheSeq = 0;
|
|
15290
15584
|
const cachePorts = cache ? {
|
|
15291
15585
|
transport: cache,
|
|
15292
15586
|
fetch: this.dependencies.cacheFetch ?? ((url2, init) => fetch(url2, init)),
|
|
15293
15587
|
runCommand: this.dependencies.runCommand,
|
|
15294
|
-
tmpFile: (suffix) =>
|
|
15588
|
+
tmpFile: (suffix) => join8(workspace, `cache-${String(cacheSeq++)}${suffix}`)
|
|
15295
15589
|
} : null;
|
|
15296
15590
|
try {
|
|
15297
15591
|
await notifyStatus(observer, "CLONING");
|
|
@@ -15334,146 +15628,194 @@ var LocalBuildExecutor = class {
|
|
|
15334
15628
|
"Git returned an invalid commit SHA."
|
|
15335
15629
|
);
|
|
15336
15630
|
}
|
|
15337
|
-
await
|
|
15631
|
+
const subject = await this.dependencies.runCommand(
|
|
15632
|
+
{
|
|
15633
|
+
command: "git",
|
|
15634
|
+
args: ["log", "-1", "--format=%s"],
|
|
15635
|
+
cwd: checkout,
|
|
15636
|
+
timeoutMs: 1e4,
|
|
15637
|
+
stage: "CLONING",
|
|
15638
|
+
errorCode: "CLONE_FAILED"
|
|
15639
|
+
},
|
|
15640
|
+
observer
|
|
15641
|
+
);
|
|
15642
|
+
const gitCommitMessage = [...subject.stdout.at(-1)?.trim() ?? ""].slice(0, 300).join("");
|
|
15643
|
+
await observer.onMetadata?.({
|
|
15644
|
+
gitCommitSha,
|
|
15645
|
+
...gitCommitMessage ? { gitCommitMessage } : {}
|
|
15646
|
+
});
|
|
15338
15647
|
const appRoot = this.dependencies.resolveAppRoot(
|
|
15339
15648
|
checkout,
|
|
15340
15649
|
input.rootDirectory
|
|
15341
15650
|
);
|
|
15342
|
-
const
|
|
15343
|
-
|
|
15344
|
-
|
|
15345
|
-
|
|
15346
|
-
|
|
15347
|
-
);
|
|
15348
|
-
const
|
|
15349
|
-
|
|
15350
|
-
|
|
15351
|
-
|
|
15352
|
-
|
|
15353
|
-
|
|
15354
|
-
|
|
15355
|
-
|
|
15356
|
-
|
|
15357
|
-
|
|
15358
|
-
|
|
15359
|
-
|
|
15360
|
-
|
|
15361
|
-
|
|
15362
|
-
|
|
15363
|
-
if (!installCommand)
|
|
15364
|
-
throw new DeploymentError(
|
|
15365
|
-
"UNSUPPORTED_PACKAGE_MANAGER",
|
|
15366
|
-
"Install command is empty."
|
|
15651
|
+
const resolved = await this.dependencies.detectFramework(appRoot, {
|
|
15652
|
+
framework: input.framework ?? null,
|
|
15653
|
+
buildCommand: input.buildCommand ?? null,
|
|
15654
|
+
outputDirectory: input.outputDirectory ?? null
|
|
15655
|
+
});
|
|
15656
|
+
await info(observer, "CLONING", `Detected ${resolved.description}`);
|
|
15657
|
+
const isNext = resolved.preset.id === "next";
|
|
15658
|
+
let platformConfig = null;
|
|
15659
|
+
let packageManagerConfigPath = null;
|
|
15660
|
+
if (isNext) {
|
|
15661
|
+
const conversion = await (this.dependencies.convertProxyToMiddleware ?? convertProxyToMiddleware)(appRoot);
|
|
15662
|
+
if (conversion.converted) {
|
|
15663
|
+
await observer.onLog({
|
|
15664
|
+
level: "WARN",
|
|
15665
|
+
stage: "CLONING",
|
|
15666
|
+
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.`
|
|
15667
|
+
});
|
|
15668
|
+
}
|
|
15669
|
+
platformConfig = await this.dependencies.createPlatformBuildConfig(
|
|
15670
|
+
appRoot,
|
|
15671
|
+
input.deploymentId
|
|
15367
15672
|
);
|
|
15368
|
-
|
|
15369
|
-
|
|
15370
|
-
|
|
15371
|
-
|
|
15372
|
-
|
|
15373
|
-
|
|
15374
|
-
|
|
15375
|
-
|
|
15376
|
-
|
|
15673
|
+
packageManagerConfigPath = platformConfig.packageManagerConfigPath;
|
|
15674
|
+
} else if (resolved.buildScript !== null) {
|
|
15675
|
+
packageManagerConfigPath = await this.dependencies.createPackageManagerConfig(appRoot);
|
|
15676
|
+
}
|
|
15677
|
+
if (resolved.buildScript !== null) {
|
|
15678
|
+
await notifyStatus(observer, "INSTALLING");
|
|
15679
|
+
const packageManager = await this.dependencies.detectPackageManager(checkout);
|
|
15680
|
+
const [installCommand, ...installArgs] = packageManager.install;
|
|
15681
|
+
if (!installCommand)
|
|
15682
|
+
throw new DeploymentError(
|
|
15683
|
+
"UNSUPPORTED_PACKAGE_MANAGER",
|
|
15684
|
+
"Install command is empty."
|
|
15685
|
+
);
|
|
15686
|
+
let depsKey = null;
|
|
15687
|
+
let depsRestored = false;
|
|
15688
|
+
if (cachePorts) {
|
|
15689
|
+
depsKey = await computeDepsCacheKey(checkout, packageManager.name);
|
|
15690
|
+
if (depsKey) {
|
|
15691
|
+
depsRestored = await tryRestore(
|
|
15692
|
+
"deps",
|
|
15693
|
+
depsKey,
|
|
15694
|
+
pmCacheDir,
|
|
15695
|
+
cachePorts,
|
|
15696
|
+
observer
|
|
15697
|
+
);
|
|
15698
|
+
}
|
|
15699
|
+
}
|
|
15700
|
+
await this.dependencies.runCommand(
|
|
15701
|
+
{
|
|
15702
|
+
command: installCommand,
|
|
15703
|
+
args: installArgs,
|
|
15704
|
+
cwd: checkout,
|
|
15705
|
+
env: {
|
|
15706
|
+
NODE_ENV: "development",
|
|
15707
|
+
NPM_CONFIG_USERCONFIG: packageManagerConfigPath,
|
|
15708
|
+
...cachePorts ? {
|
|
15709
|
+
npm_config_store_dir: pmCacheDir,
|
|
15710
|
+
npm_config_cache: pmCacheDir,
|
|
15711
|
+
YARN_CACHE_FOLDER: pmCacheDir
|
|
15712
|
+
} : {}
|
|
15713
|
+
},
|
|
15714
|
+
timeoutMs: 10 * 6e4,
|
|
15715
|
+
stage: "INSTALLING",
|
|
15716
|
+
errorCode: "INSTALL_FAILED"
|
|
15717
|
+
},
|
|
15718
|
+
observer
|
|
15719
|
+
);
|
|
15720
|
+
await notifyStatus(observer, "BUILDING");
|
|
15721
|
+
const buildCacheDir = resolved.preset.cachePath ? join8(appRoot, resolved.preset.cachePath) : null;
|
|
15722
|
+
const buildCacheKey = sanitizeCacheKey(input.branch);
|
|
15723
|
+
if (cachePorts && buildCacheDir) {
|
|
15724
|
+
await tryRestore(
|
|
15725
|
+
"next",
|
|
15726
|
+
buildCacheKey,
|
|
15727
|
+
buildCacheDir,
|
|
15377
15728
|
cachePorts,
|
|
15378
15729
|
observer
|
|
15379
15730
|
);
|
|
15380
15731
|
}
|
|
15381
|
-
|
|
15382
|
-
|
|
15383
|
-
|
|
15384
|
-
|
|
15385
|
-
|
|
15386
|
-
|
|
15387
|
-
|
|
15388
|
-
|
|
15389
|
-
|
|
15390
|
-
|
|
15391
|
-
|
|
15392
|
-
|
|
15393
|
-
|
|
15394
|
-
} : {}
|
|
15395
|
-
},
|
|
15396
|
-
timeoutMs: 10 * 6e4,
|
|
15397
|
-
stage: "INSTALLING",
|
|
15398
|
-
errorCode: "INSTALL_FAILED"
|
|
15399
|
-
},
|
|
15400
|
-
observer
|
|
15401
|
-
);
|
|
15402
|
-
await notifyStatus(observer, "BUILDING");
|
|
15403
|
-
const nextCacheDir = join7(appRoot, ".next", "cache");
|
|
15404
|
-
const nextKey = sanitizeCacheKey(input.branch);
|
|
15405
|
-
if (cachePorts) {
|
|
15406
|
-
await tryRestore("next", nextKey, nextCacheDir, cachePorts, observer);
|
|
15407
|
-
}
|
|
15408
|
-
await this.dependencies.runCommand(
|
|
15409
|
-
{
|
|
15410
|
-
command: packageManager.name,
|
|
15411
|
-
args: ["run", project.buildScript],
|
|
15412
|
-
cwd: appRoot,
|
|
15413
|
-
env: {
|
|
15414
|
-
...input.env ?? {},
|
|
15415
|
-
NEXT_PRIVATE_STANDALONE: "true",
|
|
15416
|
-
NPM_CONFIG_USERCONFIG: platformConfig.packageManagerConfigPath
|
|
15417
|
-
},
|
|
15418
|
-
timeoutMs: 15 * 6e4,
|
|
15419
|
-
stage: "BUILDING",
|
|
15420
|
-
errorCode: "BUILD_FAILED"
|
|
15421
|
-
},
|
|
15422
|
-
observer
|
|
15423
|
-
);
|
|
15424
|
-
await this.dependencies.runCommand(
|
|
15425
|
-
{
|
|
15426
|
-
command: this.dependencies.openNextCommand,
|
|
15427
|
-
args: [
|
|
15428
|
-
"build",
|
|
15429
|
-
"--skip-next-build",
|
|
15430
|
-
"--openNextConfigPath",
|
|
15431
|
-
platformConfig.openNextConfigPath,
|
|
15432
|
-
"--config",
|
|
15433
|
-
platformConfig.wranglerConfigPath
|
|
15434
|
-
],
|
|
15435
|
-
cwd: appRoot,
|
|
15436
|
-
timeoutMs: 15 * 6e4,
|
|
15437
|
-
stage: "BUILDING",
|
|
15438
|
-
errorCode: "OPENNEXT_FAILED"
|
|
15439
|
-
},
|
|
15440
|
-
observer
|
|
15441
|
-
);
|
|
15442
|
-
await this.dependencies.runCommand(
|
|
15443
|
-
{
|
|
15444
|
-
command: this.dependencies.wranglerCommand,
|
|
15445
|
-
args: [
|
|
15446
|
-
"deploy",
|
|
15447
|
-
"--dry-run",
|
|
15448
|
-
"--config",
|
|
15449
|
-
platformConfig.wranglerConfigPath,
|
|
15450
|
-
"--outdir",
|
|
15451
|
-
".open-next/worker-bundle"
|
|
15452
|
-
],
|
|
15453
|
-
cwd: appRoot,
|
|
15454
|
-
env: {
|
|
15455
|
-
WRANGLER_LOG_PATH: join7(appRoot, ".open-next", "wrangler.log")
|
|
15732
|
+
await this.dependencies.runCommand(
|
|
15733
|
+
{
|
|
15734
|
+
command: packageManager.name,
|
|
15735
|
+
args: ["run", resolved.buildScript],
|
|
15736
|
+
cwd: appRoot,
|
|
15737
|
+
env: {
|
|
15738
|
+
...input.env ?? {},
|
|
15739
|
+
...isNext ? { NEXT_PRIVATE_STANDALONE: "true" } : {},
|
|
15740
|
+
NPM_CONFIG_USERCONFIG: packageManagerConfigPath
|
|
15741
|
+
},
|
|
15742
|
+
timeoutMs: 15 * 6e4,
|
|
15743
|
+
stage: "BUILDING",
|
|
15744
|
+
errorCode: "BUILD_FAILED"
|
|
15456
15745
|
},
|
|
15457
|
-
|
|
15458
|
-
|
|
15459
|
-
|
|
15460
|
-
|
|
15461
|
-
|
|
15462
|
-
|
|
15463
|
-
|
|
15464
|
-
|
|
15465
|
-
|
|
15746
|
+
observer
|
|
15747
|
+
);
|
|
15748
|
+
if (isNext) {
|
|
15749
|
+
await this.dependencies.runCommand(
|
|
15750
|
+
{
|
|
15751
|
+
command: this.dependencies.openNextCommand,
|
|
15752
|
+
args: [
|
|
15753
|
+
"build",
|
|
15754
|
+
"--skip-next-build",
|
|
15755
|
+
"--openNextConfigPath",
|
|
15756
|
+
platformConfig.openNextConfigPath,
|
|
15757
|
+
"--config",
|
|
15758
|
+
platformConfig.wranglerConfigPath
|
|
15759
|
+
],
|
|
15760
|
+
cwd: appRoot,
|
|
15761
|
+
timeoutMs: 15 * 6e4,
|
|
15762
|
+
stage: "BUILDING",
|
|
15763
|
+
errorCode: "OPENNEXT_FAILED"
|
|
15764
|
+
},
|
|
15765
|
+
observer
|
|
15766
|
+
);
|
|
15767
|
+
await this.dependencies.runCommand(
|
|
15768
|
+
{
|
|
15769
|
+
command: this.dependencies.wranglerCommand,
|
|
15770
|
+
args: [
|
|
15771
|
+
"deploy",
|
|
15772
|
+
"--dry-run",
|
|
15773
|
+
"--config",
|
|
15774
|
+
platformConfig.wranglerConfigPath,
|
|
15775
|
+
"--outdir",
|
|
15776
|
+
".open-next/worker-bundle"
|
|
15777
|
+
],
|
|
15778
|
+
cwd: appRoot,
|
|
15779
|
+
env: {
|
|
15780
|
+
WRANGLER_LOG_PATH: join8(appRoot, ".open-next", "wrangler.log")
|
|
15781
|
+
},
|
|
15782
|
+
timeoutMs: 5 * 6e4,
|
|
15783
|
+
stage: "BUILDING",
|
|
15784
|
+
errorCode: "OPENNEXT_FAILED"
|
|
15785
|
+
},
|
|
15786
|
+
observer
|
|
15787
|
+
);
|
|
15788
|
+
}
|
|
15789
|
+
if (cachePorts) {
|
|
15790
|
+
if (depsKey && !depsRestored) {
|
|
15791
|
+
await trySave("deps", depsKey, pmCacheDir, cachePorts, observer);
|
|
15792
|
+
}
|
|
15793
|
+
if (buildCacheDir) {
|
|
15794
|
+
await trySave(
|
|
15795
|
+
"next",
|
|
15796
|
+
buildCacheKey,
|
|
15797
|
+
buildCacheDir,
|
|
15798
|
+
cachePorts,
|
|
15799
|
+
observer
|
|
15800
|
+
);
|
|
15801
|
+
}
|
|
15466
15802
|
}
|
|
15467
|
-
await trySave("next", nextKey, nextCacheDir, cachePorts, observer);
|
|
15468
15803
|
}
|
|
15469
15804
|
await notifyStatus(observer, "DEPLOYING");
|
|
15470
|
-
const
|
|
15805
|
+
const identity = {
|
|
15806
|
+
deploymentId: input.deploymentId,
|
|
15807
|
+
projectId: input.projectId
|
|
15808
|
+
};
|
|
15809
|
+
const artifact = isNext ? await this.dependencies.readOpenNextArtifact(
|
|
15471
15810
|
appRoot,
|
|
15472
|
-
|
|
15473
|
-
deploymentId: input.deploymentId,
|
|
15474
|
-
projectId: input.projectId
|
|
15475
|
-
},
|
|
15811
|
+
identity,
|
|
15476
15812
|
platformConfig.wranglerConfigPath
|
|
15813
|
+
) : await this.dependencies.readStaticArtifact(
|
|
15814
|
+
appRoot,
|
|
15815
|
+
resolved.outputDirectory,
|
|
15816
|
+
identity,
|
|
15817
|
+
{ spaFallback: resolved.preset.spaFallback },
|
|
15818
|
+
(message) => observer.onLog({ level: "WARN", stage: "DEPLOYING", message })
|
|
15477
15819
|
);
|
|
15478
15820
|
return { gitCommitSha, artifact };
|
|
15479
15821
|
} finally {
|
|
@@ -15482,49 +15824,66 @@ var LocalBuildExecutor = class {
|
|
|
15482
15824
|
}
|
|
15483
15825
|
};
|
|
15484
15826
|
|
|
15485
|
-
// src/
|
|
15486
|
-
import {
|
|
15487
|
-
|
|
15488
|
-
|
|
15489
|
-
|
|
15490
|
-
|
|
15491
|
-
|
|
15492
|
-
|
|
15493
|
-
|
|
15494
|
-
|
|
15495
|
-
|
|
15496
|
-
|
|
15497
|
-
|
|
15498
|
-
|
|
15499
|
-
|
|
15500
|
-
|
|
15501
|
-
|
|
15502
|
-
|
|
15503
|
-
|
|
15827
|
+
// ../../packages/sdk/src/auth.ts
|
|
15828
|
+
import { homedir as homedir2 } from "os";
|
|
15829
|
+
|
|
15830
|
+
// ../../packages/sdk/src/errors.ts
|
|
15831
|
+
var NaroError = class extends Error {
|
|
15832
|
+
code;
|
|
15833
|
+
/** HTTP status of the control plane response, when one was received. */
|
|
15834
|
+
status;
|
|
15835
|
+
/** Extra structured context (validation fields, deployment id...). */
|
|
15836
|
+
details;
|
|
15837
|
+
constructor(code, message, options) {
|
|
15838
|
+
super(
|
|
15839
|
+
message,
|
|
15840
|
+
options?.cause !== void 0 ? { cause: options.cause } : {}
|
|
15841
|
+
);
|
|
15842
|
+
this.name = "NaroError";
|
|
15843
|
+
this.code = code;
|
|
15844
|
+
if (options?.status !== void 0) this.status = options.status;
|
|
15845
|
+
if (options?.details !== void 0) this.details = options.details;
|
|
15504
15846
|
}
|
|
15847
|
+
};
|
|
15848
|
+
function isNaroError(error51) {
|
|
15849
|
+
return error51 instanceof NaroError || error51 instanceof Error && error51.name === "NaroError" && typeof error51.code === "string";
|
|
15505
15850
|
}
|
|
15506
|
-
|
|
15507
|
-
|
|
15508
|
-
const
|
|
15509
|
-
return {
|
|
15510
|
-
|
|
15511
|
-
|
|
15512
|
-
|
|
15851
|
+
function toNaroError(error51, fallbackCode = "INTERNAL_ERROR") {
|
|
15852
|
+
if (error51 instanceof NaroError) return error51;
|
|
15853
|
+
const message = error51 instanceof Error ? error51.message : String(error51);
|
|
15854
|
+
return new NaroError(fallbackCode, message, { cause: error51 });
|
|
15855
|
+
}
|
|
15856
|
+
async function errorFromResponse(response, context) {
|
|
15857
|
+
let body = null;
|
|
15858
|
+
try {
|
|
15859
|
+
body = await response.json();
|
|
15860
|
+
} catch {
|
|
15861
|
+
body = null;
|
|
15862
|
+
}
|
|
15863
|
+
const serverCode = typeof body?.error?.code === "string" ? body.error.code : null;
|
|
15864
|
+
const serverMessage = typeof body?.error?.message === "string" ? body.error.message : null;
|
|
15865
|
+
const code = serverCode ?? (response.status === 401 ? "UNAUTHORIZED" : response.status === 404 ? "NOT_FOUND" : response.status === 429 ? "RATE_LIMITED" : "INTERNAL_ERROR");
|
|
15866
|
+
const details = {};
|
|
15867
|
+
if (body?.error?.fields !== void 0) details.fields = body.error.fields;
|
|
15868
|
+
return new NaroError(
|
|
15869
|
+
code,
|
|
15870
|
+
serverMessage ?? `${context} failed: the control plane returned ${String(response.status)}.`,
|
|
15871
|
+
{
|
|
15872
|
+
status: response.status,
|
|
15873
|
+
...Object.keys(details).length > 0 ? { details } : {}
|
|
15874
|
+
}
|
|
15875
|
+
);
|
|
15513
15876
|
}
|
|
15514
15877
|
|
|
15515
|
-
// src/
|
|
15516
|
-
import { spawn as spawn2 } from "child_process";
|
|
15517
|
-
import { homedir as homedir2 } from "os";
|
|
15518
|
-
|
|
15519
|
-
// src/session.ts
|
|
15878
|
+
// ../../packages/sdk/src/session.ts
|
|
15520
15879
|
import { mkdirSync, readFileSync, rmSync, writeFileSync } from "fs";
|
|
15521
15880
|
import { homedir } from "os";
|
|
15522
|
-
import { join as
|
|
15881
|
+
import { join as join9 } from "path";
|
|
15523
15882
|
function credentialsDir(home = homedir()) {
|
|
15524
|
-
return
|
|
15883
|
+
return join9(home, ".naro");
|
|
15525
15884
|
}
|
|
15526
15885
|
function credentialsFile(home = homedir()) {
|
|
15527
|
-
return
|
|
15886
|
+
return join9(credentialsDir(home), "credentials.json");
|
|
15528
15887
|
}
|
|
15529
15888
|
function saveSession(session, home = homedir()) {
|
|
15530
15889
|
mkdirSync(credentialsDir(home), { recursive: true, mode: 448 });
|
|
@@ -15548,20 +15907,31 @@ function isExpired(session, skewSeconds = 60) {
|
|
|
15548
15907
|
return session.expires_at * 1e3 <= Date.now() + skewSeconds * 1e3;
|
|
15549
15908
|
}
|
|
15550
15909
|
|
|
15551
|
-
// src/supabase-auth.ts
|
|
15910
|
+
// ../../packages/sdk/src/supabase-auth.ts
|
|
15552
15911
|
import {
|
|
15553
15912
|
createClient
|
|
15554
15913
|
} from "@supabase/supabase-js";
|
|
15555
15914
|
|
|
15556
|
-
// src/
|
|
15915
|
+
// ../../packages/sdk/src/config.ts
|
|
15916
|
+
var DEFAULT_CONTROL_PLANE_URL = "https://naro.sh";
|
|
15917
|
+
var DEFAULT_SUPABASE_URL = "https://bymwzjpqfmwsdhtcpgjg.supabase.co";
|
|
15918
|
+
var DEFAULT_SUPABASE_KEY = "sb_publishable_qjM6NZsHnGZ1t6iwuTDwcQ_LQbq4aCX";
|
|
15919
|
+
function controlPlaneUrl(env = process.env) {
|
|
15920
|
+
return (env.NARO_CONTROL_PLANE_URL ?? DEFAULT_CONTROL_PLANE_URL).replace(
|
|
15921
|
+
/\/$/,
|
|
15922
|
+
""
|
|
15923
|
+
);
|
|
15924
|
+
}
|
|
15925
|
+
|
|
15926
|
+
// ../../packages/sdk/src/callback-server.ts
|
|
15557
15927
|
import { createServer } from "http";
|
|
15558
15928
|
var DONE_HTML = "<html><body><p>\u2705 Logged in. You can close this tab and return to the terminal.</p></body></html>";
|
|
15559
15929
|
var FAIL_HTML = "<html><body><p>Login failed. You can close this tab and return to the terminal.</p></body></html>";
|
|
15560
15930
|
async function startCallbackServer() {
|
|
15561
15931
|
let resolveCode;
|
|
15562
15932
|
let rejectCode;
|
|
15563
|
-
const codePromise = new Promise((
|
|
15564
|
-
resolveCode =
|
|
15933
|
+
const codePromise = new Promise((resolve4, reject) => {
|
|
15934
|
+
resolveCode = resolve4;
|
|
15565
15935
|
rejectCode = reject;
|
|
15566
15936
|
});
|
|
15567
15937
|
const server = createServer((req, res) => {
|
|
@@ -15588,7 +15958,7 @@ async function startCallbackServer() {
|
|
|
15588
15958
|
res.end(DONE_HTML);
|
|
15589
15959
|
resolveCode(code);
|
|
15590
15960
|
});
|
|
15591
|
-
await new Promise((
|
|
15961
|
+
await new Promise((resolve4) => server.listen(0, "127.0.0.1", resolve4));
|
|
15592
15962
|
const port = server.address().port;
|
|
15593
15963
|
return {
|
|
15594
15964
|
port,
|
|
@@ -15611,12 +15981,10 @@ async function startCallbackServer() {
|
|
|
15611
15981
|
};
|
|
15612
15982
|
}
|
|
15613
15983
|
|
|
15614
|
-
// src/supabase-auth.ts
|
|
15615
|
-
var DEFAULT_URL = "https://bymwzjpqfmwsdhtcpgjg.supabase.co";
|
|
15616
|
-
var DEFAULT_KEY = "sb_publishable_qjM6NZsHnGZ1t6iwuTDwcQ_LQbq4aCX";
|
|
15984
|
+
// ../../packages/sdk/src/supabase-auth.ts
|
|
15617
15985
|
function createAuthClient(env = process.env) {
|
|
15618
|
-
const url2 = env.NARO_SUPABASE_URL ??
|
|
15619
|
-
const key = env.NARO_SUPABASE_KEY ??
|
|
15986
|
+
const url2 = env.NARO_SUPABASE_URL ?? DEFAULT_SUPABASE_URL;
|
|
15987
|
+
const key = env.NARO_SUPABASE_KEY ?? DEFAULT_SUPABASE_KEY;
|
|
15620
15988
|
const store = /* @__PURE__ */ new Map();
|
|
15621
15989
|
return createClient(url2, key, {
|
|
15622
15990
|
auth: {
|
|
@@ -15689,69 +16057,659 @@ async function login(deps) {
|
|
|
15689
16057
|
}
|
|
15690
16058
|
}
|
|
15691
16059
|
|
|
15692
|
-
// src/
|
|
15693
|
-
|
|
15694
|
-
|
|
15695
|
-
|
|
15696
|
-
|
|
15697
|
-
|
|
15698
|
-
};
|
|
16060
|
+
// ../../packages/sdk/src/auth.ts
|
|
16061
|
+
async function resolveAuth(options = {}) {
|
|
16062
|
+
const env = options.env ?? process.env;
|
|
16063
|
+
const home = options.home ?? homedir2();
|
|
16064
|
+
if (options.explicitToken) {
|
|
16065
|
+
const token = options.explicitToken;
|
|
16066
|
+
return { getAccessToken: async () => token, source: "flag" };
|
|
16067
|
+
}
|
|
16068
|
+
if (env.NARO_TOKEN) {
|
|
16069
|
+
const token = env.NARO_TOKEN;
|
|
16070
|
+
return { getAccessToken: async () => token, source: "env" };
|
|
16071
|
+
}
|
|
16072
|
+
const refresh = options.refresh ?? refreshStoredSession;
|
|
16073
|
+
let session = loadStoredSession(home);
|
|
16074
|
+
if (!session || isExpired(session)) {
|
|
16075
|
+
if (session) {
|
|
16076
|
+
try {
|
|
16077
|
+
session = await refresh(session.refresh_token);
|
|
16078
|
+
saveSession(session, home);
|
|
16079
|
+
} catch {
|
|
16080
|
+
session = null;
|
|
16081
|
+
}
|
|
16082
|
+
}
|
|
16083
|
+
if (!session) {
|
|
16084
|
+
const interactive = options.interactiveLogin;
|
|
16085
|
+
if (!interactive) {
|
|
16086
|
+
throw new NaroError(
|
|
16087
|
+
"NOT_LOGGED_IN",
|
|
16088
|
+
"Not logged in. Run `naro login`, or set NARO_TOKEN."
|
|
16089
|
+
);
|
|
16090
|
+
}
|
|
16091
|
+
interactive.log(
|
|
16092
|
+
"Not logged in \u2014 opening your browser to sign in with GitHub\u2026"
|
|
16093
|
+
);
|
|
16094
|
+
const loginFlow = options.loginFlow ?? ((openBrowser2) => login({ openBrowser: openBrowser2 }));
|
|
16095
|
+
session = await loginFlow((url2) => {
|
|
16096
|
+
interactive.log(`If your browser did not open, visit:
|
|
16097
|
+
${url2}`);
|
|
16098
|
+
interactive.openBrowser(url2);
|
|
16099
|
+
});
|
|
16100
|
+
saveSession(session, home);
|
|
16101
|
+
}
|
|
16102
|
+
}
|
|
16103
|
+
const auth = {
|
|
16104
|
+
source: "session",
|
|
16105
|
+
user: session.user,
|
|
16106
|
+
getAccessToken: async () => {
|
|
16107
|
+
const current = loadStoredSession(home);
|
|
16108
|
+
if (!current) {
|
|
16109
|
+
throw new NaroError(
|
|
16110
|
+
"NOT_LOGGED_IN",
|
|
16111
|
+
"The session was logged out. Run `naro login` again."
|
|
16112
|
+
);
|
|
16113
|
+
}
|
|
16114
|
+
if (!isExpired(current)) return current.access_token;
|
|
16115
|
+
const refreshed = await refresh(current.refresh_token);
|
|
16116
|
+
saveSession(refreshed, home);
|
|
16117
|
+
return refreshed.access_token;
|
|
16118
|
+
}
|
|
16119
|
+
};
|
|
16120
|
+
return auth;
|
|
16121
|
+
}
|
|
16122
|
+
|
|
16123
|
+
// ../../packages/sdk/src/browser.ts
|
|
16124
|
+
import { spawn as spawn2 } from "child_process";
|
|
15699
16125
|
function openBrowser(url2) {
|
|
15700
16126
|
const platform = process.platform;
|
|
15701
16127
|
const command = platform === "darwin" ? "open" : platform === "win32" ? "cmd" : "xdg-open";
|
|
15702
16128
|
const args = platform === "win32" ? ["/c", "start", "", url2] : [url2];
|
|
15703
|
-
spawn2(command, args, { stdio: "ignore", detached: true })
|
|
15704
|
-
|
|
15705
|
-
async function runLogin(io = stdio) {
|
|
15706
|
-
io.out("Opening your browser to sign in with GitHub...");
|
|
15707
|
-
const session = await login({
|
|
15708
|
-
openBrowser: (url2) => {
|
|
15709
|
-
io.out(`If your browser did not open, visit:
|
|
15710
|
-
${url2}`);
|
|
15711
|
-
openBrowser(url2);
|
|
15712
|
-
}
|
|
16129
|
+
const child = spawn2(command, args, { stdio: "ignore", detached: true });
|
|
16130
|
+
child.on("error", () => {
|
|
15713
16131
|
});
|
|
15714
|
-
|
|
15715
|
-
io.out(`\u2705 Logged in as ${session.user.email ?? session.user.id}`);
|
|
16132
|
+
child.unref();
|
|
15716
16133
|
}
|
|
15717
|
-
|
|
15718
|
-
|
|
15719
|
-
|
|
15720
|
-
|
|
15721
|
-
|
|
15722
|
-
|
|
15723
|
-
|
|
15724
|
-
|
|
15725
|
-
|
|
15726
|
-
|
|
15727
|
-
|
|
15728
|
-
|
|
15729
|
-
|
|
15730
|
-
|
|
15731
|
-
|
|
16134
|
+
|
|
16135
|
+
// ../../packages/sdk/src/types.ts
|
|
16136
|
+
var TERMINAL_DEPLOYMENT_STATUSES = [
|
|
16137
|
+
"READY",
|
|
16138
|
+
"FAILED"
|
|
16139
|
+
];
|
|
16140
|
+
function isTerminalStatus(status) {
|
|
16141
|
+
return TERMINAL_DEPLOYMENT_STATUSES.includes(status);
|
|
16142
|
+
}
|
|
16143
|
+
function projectInfo(row) {
|
|
16144
|
+
return {
|
|
16145
|
+
id: row.id,
|
|
16146
|
+
name: row.name,
|
|
16147
|
+
slug: row.slug,
|
|
16148
|
+
subdomain: row.subdomain,
|
|
16149
|
+
teamId: row.team_id,
|
|
16150
|
+
defaultBranch: row.default_branch,
|
|
16151
|
+
rootDirectory: row.root_directory,
|
|
16152
|
+
repositoryUrl: row.repository_url,
|
|
16153
|
+
repositoryFullName: row.repository_full_name ?? null,
|
|
16154
|
+
createdAt: row.created_at,
|
|
16155
|
+
updatedAt: row.updated_at
|
|
16156
|
+
};
|
|
16157
|
+
}
|
|
16158
|
+
function deploymentInfo(row) {
|
|
16159
|
+
return {
|
|
16160
|
+
id: row.id,
|
|
16161
|
+
projectId: row.project_id,
|
|
16162
|
+
status: row.status,
|
|
16163
|
+
branch: row.branch,
|
|
16164
|
+
url: row.preview_url,
|
|
16165
|
+
artifactKey: row.artifact_key ?? null,
|
|
16166
|
+
errorCode: row.error_code,
|
|
16167
|
+
errorMessage: row.error_message,
|
|
16168
|
+
gitCommitSha: row.git_commit_sha,
|
|
16169
|
+
gitCommitMessage: row.git_commit_message,
|
|
16170
|
+
createdAt: row.created_at,
|
|
16171
|
+
startedAt: row.started_at,
|
|
16172
|
+
finishedAt: row.finished_at
|
|
16173
|
+
};
|
|
16174
|
+
}
|
|
16175
|
+
function buildLogEntry(row) {
|
|
16176
|
+
return {
|
|
16177
|
+
sequence: row.sequence,
|
|
16178
|
+
level: row.level,
|
|
16179
|
+
stage: row.stage,
|
|
16180
|
+
message: row.message,
|
|
16181
|
+
createdAt: row.created_at
|
|
16182
|
+
};
|
|
16183
|
+
}
|
|
16184
|
+
|
|
16185
|
+
// ../../packages/sdk/src/client.ts
|
|
16186
|
+
var NaroClient = class {
|
|
16187
|
+
baseUrl;
|
|
16188
|
+
getAccessToken;
|
|
16189
|
+
doFetch;
|
|
16190
|
+
teams;
|
|
16191
|
+
projects;
|
|
16192
|
+
deployments;
|
|
16193
|
+
env;
|
|
16194
|
+
domains;
|
|
16195
|
+
logs;
|
|
16196
|
+
constructor(options) {
|
|
16197
|
+
this.baseUrl = (options.baseUrl ?? controlPlaneUrl()).replace(/\/$/, "");
|
|
16198
|
+
this.getAccessToken = options.getAccessToken;
|
|
16199
|
+
this.doFetch = options.fetch ?? fetch;
|
|
16200
|
+
this.teams = new TeamsApi(this);
|
|
16201
|
+
this.projects = new ProjectsApi(this);
|
|
16202
|
+
this.deployments = new DeploymentsApi(this);
|
|
16203
|
+
this.env = new EnvApi(this);
|
|
16204
|
+
this.domains = new DomainsApi(this);
|
|
16205
|
+
this.logs = new LogsApi(this);
|
|
16206
|
+
}
|
|
16207
|
+
async request(path, options) {
|
|
16208
|
+
const token = await this.getAccessToken();
|
|
16209
|
+
let response;
|
|
16210
|
+
try {
|
|
16211
|
+
response = await this.doFetch(`${this.baseUrl}${path}`, {
|
|
16212
|
+
method: options.method ?? "GET",
|
|
16213
|
+
headers: {
|
|
16214
|
+
Authorization: `Bearer ${token}`,
|
|
16215
|
+
...options.body !== void 0 ? { "Content-Type": "application/json" } : {}
|
|
16216
|
+
},
|
|
16217
|
+
...options.body !== void 0 ? { body: JSON.stringify(options.body) } : {}
|
|
16218
|
+
});
|
|
16219
|
+
} catch (error51) {
|
|
16220
|
+
throw new NaroError(
|
|
16221
|
+
"NETWORK_ERROR",
|
|
16222
|
+
`${options.context} failed: could not reach ${this.baseUrl}.`,
|
|
16223
|
+
{ cause: error51 }
|
|
16224
|
+
);
|
|
15732
16225
|
}
|
|
16226
|
+
if (!response.ok) throw await errorFromResponse(response, options.context);
|
|
16227
|
+
return await response.json();
|
|
16228
|
+
}
|
|
16229
|
+
};
|
|
16230
|
+
var TeamsApi = class {
|
|
16231
|
+
constructor(client) {
|
|
16232
|
+
this.client = client;
|
|
16233
|
+
}
|
|
16234
|
+
async list() {
|
|
16235
|
+
const body = await this.client.request(
|
|
16236
|
+
"/api/teams",
|
|
16237
|
+
{ context: "List teams" }
|
|
16238
|
+
);
|
|
16239
|
+
return body.teams;
|
|
16240
|
+
}
|
|
16241
|
+
};
|
|
16242
|
+
var ProjectsApi = class {
|
|
16243
|
+
constructor(client) {
|
|
16244
|
+
this.client = client;
|
|
16245
|
+
}
|
|
16246
|
+
async list(teamSlug) {
|
|
16247
|
+
const body = await this.client.request(
|
|
16248
|
+
`/api/projects?team=${encodeURIComponent(teamSlug)}`,
|
|
16249
|
+
{ context: "List projects" }
|
|
16250
|
+
);
|
|
16251
|
+
return body.projects.map(projectInfo);
|
|
16252
|
+
}
|
|
16253
|
+
async get(projectId) {
|
|
16254
|
+
const body = await this.client.request(
|
|
16255
|
+
`/api/projects/${encodeURIComponent(projectId)}`,
|
|
16256
|
+
{ context: "Read project" }
|
|
16257
|
+
);
|
|
16258
|
+
return projectInfo(body.project);
|
|
16259
|
+
}
|
|
16260
|
+
};
|
|
16261
|
+
var DeploymentsApi = class {
|
|
16262
|
+
constructor(client) {
|
|
16263
|
+
this.client = client;
|
|
16264
|
+
}
|
|
16265
|
+
async list(projectId) {
|
|
16266
|
+
const body = await this.client.request(`/api/projects/${encodeURIComponent(projectId)}/deployments`, {
|
|
16267
|
+
context: "List deployments"
|
|
16268
|
+
});
|
|
16269
|
+
return body.deployments.map(deploymentInfo);
|
|
16270
|
+
}
|
|
16271
|
+
async get(deploymentId) {
|
|
16272
|
+
const body = await this.client.request(`/api/deployments/${encodeURIComponent(deploymentId)}`, {
|
|
16273
|
+
context: "Read deployment"
|
|
16274
|
+
});
|
|
16275
|
+
return {
|
|
16276
|
+
deployment: deploymentInfo(body.deployment),
|
|
16277
|
+
logs: body.logs.map(buildLogEntry)
|
|
16278
|
+
};
|
|
15733
16279
|
}
|
|
15734
|
-
|
|
15735
|
-
|
|
16280
|
+
/**
|
|
16281
|
+
* Starts a CLI deployment: the control plane creates/loads the project,
|
|
16282
|
+
* records the deployment and returns a callback token; the caller builds
|
|
16283
|
+
* locally and uploads the artifact.
|
|
16284
|
+
*/
|
|
16285
|
+
async createCli(input) {
|
|
16286
|
+
return this.client.request("/api/cli/deployments", {
|
|
16287
|
+
method: "POST",
|
|
16288
|
+
body: input,
|
|
16289
|
+
context: "Create deployment"
|
|
16290
|
+
});
|
|
16291
|
+
}
|
|
16292
|
+
/**
|
|
16293
|
+
* Queues a server-side git build (GitHub-connected projects): clone,
|
|
16294
|
+
* build and deploy run on the platform.
|
|
16295
|
+
*/
|
|
16296
|
+
async createRemote(projectId, options = {}) {
|
|
16297
|
+
const body = await this.client.request(
|
|
16298
|
+
`/api/projects/${encodeURIComponent(projectId)}/deployments`,
|
|
16299
|
+
{
|
|
16300
|
+
method: "POST",
|
|
16301
|
+
// createDeploymentSchema accepts string | undefined — never null.
|
|
16302
|
+
body: options.branch ? { branch: options.branch } : {},
|
|
16303
|
+
context: "Create deployment"
|
|
16304
|
+
}
|
|
16305
|
+
);
|
|
16306
|
+
return deploymentInfo(body.deployment);
|
|
16307
|
+
}
|
|
16308
|
+
/**
|
|
16309
|
+
* Picks the deployment `naro rollback` restores: the newest READY
|
|
16310
|
+
* production deployment serving a DIFFERENT artifact than the current one.
|
|
16311
|
+
* Comparing artifacts (not row order) keeps repeated rollbacks from
|
|
16312
|
+
* ping-ponging between the same two rows — promoted rows share their
|
|
16313
|
+
* source's artifact.
|
|
16314
|
+
*/
|
|
16315
|
+
async rollbackTarget(projectId, defaultBranch) {
|
|
16316
|
+
const deployments = await this.list(projectId);
|
|
16317
|
+
const production = deployments.filter(
|
|
16318
|
+
(deployment) => deployment.status === "READY" && deployment.branch === defaultBranch
|
|
16319
|
+
);
|
|
16320
|
+
const current = production[0];
|
|
16321
|
+
if (!current) {
|
|
16322
|
+
throw new NaroError(
|
|
16323
|
+
"DEPLOYMENT_NOT_FOUND",
|
|
16324
|
+
"No production deployment to roll back from."
|
|
16325
|
+
);
|
|
16326
|
+
}
|
|
16327
|
+
const differs = (candidate) => current.artifactKey && candidate.artifactKey ? candidate.artifactKey !== current.artifactKey : candidate.id !== current.id;
|
|
16328
|
+
return { current, target: production.slice(1).find(differs) ?? null };
|
|
16329
|
+
}
|
|
16330
|
+
/**
|
|
16331
|
+
* Redeploys an existing READY deployment's artifact to production.
|
|
16332
|
+
* Used by both `naro promote` (preview → production) and `naro rollback`
|
|
16333
|
+
* (older production build → production).
|
|
16334
|
+
*/
|
|
16335
|
+
async promote(projectId, deploymentId) {
|
|
16336
|
+
const body = await this.client.request(
|
|
16337
|
+
`/api/projects/${encodeURIComponent(projectId)}/deployments/${encodeURIComponent(deploymentId)}/promote`,
|
|
16338
|
+
{ method: "POST", context: "Promote deployment" }
|
|
16339
|
+
);
|
|
16340
|
+
return deploymentInfo(body.deployment);
|
|
16341
|
+
}
|
|
16342
|
+
/** Polls a deployment until READY/FAILED, streaming new build logs. */
|
|
16343
|
+
async wait(deploymentId, options = {}) {
|
|
16344
|
+
const intervalMs = options.intervalMs ?? 2500;
|
|
16345
|
+
const timeoutMs = options.timeoutMs ?? 15 * 6e4;
|
|
16346
|
+
const sleep = options.sleep ?? ((ms) => new Promise((resolve4) => setTimeout(resolve4, ms)));
|
|
16347
|
+
const startedAt = Date.now();
|
|
16348
|
+
let lastStatus = null;
|
|
16349
|
+
let lastSequence = -1;
|
|
16350
|
+
for (; ; ) {
|
|
16351
|
+
const detail = await this.get(deploymentId);
|
|
16352
|
+
for (const log2 of detail.logs) {
|
|
16353
|
+
if (log2.sequence > lastSequence) {
|
|
16354
|
+
lastSequence = log2.sequence;
|
|
16355
|
+
options.onLog?.(log2);
|
|
16356
|
+
}
|
|
16357
|
+
}
|
|
16358
|
+
if (detail.deployment.status !== lastStatus) {
|
|
16359
|
+
lastStatus = detail.deployment.status;
|
|
16360
|
+
options.onStatus?.(lastStatus);
|
|
16361
|
+
}
|
|
16362
|
+
if (isTerminalStatus(detail.deployment.status)) return detail;
|
|
16363
|
+
if (Date.now() - startedAt > timeoutMs) {
|
|
16364
|
+
throw new NaroError(
|
|
16365
|
+
"TIMEOUT",
|
|
16366
|
+
`Deployment ${deploymentId} did not finish within ${String(Math.round(timeoutMs / 1e3))}s.`,
|
|
16367
|
+
{ details: { deploymentId } }
|
|
16368
|
+
);
|
|
16369
|
+
}
|
|
16370
|
+
await sleep(intervalMs);
|
|
16371
|
+
}
|
|
16372
|
+
}
|
|
16373
|
+
};
|
|
16374
|
+
var EnvApi = class {
|
|
16375
|
+
constructor(client) {
|
|
16376
|
+
this.client = client;
|
|
16377
|
+
}
|
|
16378
|
+
async list(projectId, options = {}) {
|
|
16379
|
+
const query = options.reveal ? "?reveal=true" : "";
|
|
16380
|
+
const body = await this.client.request(
|
|
16381
|
+
`/api/projects/${encodeURIComponent(projectId)}/env${query}`,
|
|
16382
|
+
{ context: "List environment variables" }
|
|
16383
|
+
);
|
|
16384
|
+
return body.envVars;
|
|
16385
|
+
}
|
|
16386
|
+
async set(projectId, entries) {
|
|
16387
|
+
await this.client.request(
|
|
16388
|
+
`/api/projects/${encodeURIComponent(projectId)}/env`,
|
|
16389
|
+
{
|
|
16390
|
+
method: "POST",
|
|
16391
|
+
body: {
|
|
16392
|
+
entries: entries.map((entry) => ({
|
|
16393
|
+
key: entry.key,
|
|
16394
|
+
value: entry.value,
|
|
16395
|
+
target: entry.target ?? "all",
|
|
16396
|
+
isSecret: entry.isSecret ?? true
|
|
16397
|
+
}))
|
|
16398
|
+
},
|
|
16399
|
+
context: "Set environment variable"
|
|
16400
|
+
}
|
|
16401
|
+
);
|
|
16402
|
+
}
|
|
16403
|
+
async removeById(projectId, envVarId) {
|
|
16404
|
+
await this.client.request(
|
|
16405
|
+
`/api/projects/${encodeURIComponent(projectId)}/env/${encodeURIComponent(envVarId)}`,
|
|
16406
|
+
{ method: "DELETE", context: "Remove environment variable" }
|
|
16407
|
+
);
|
|
16408
|
+
}
|
|
16409
|
+
/** Removes every row for a key (optionally narrowed to one target). */
|
|
16410
|
+
async remove(projectId, key, target) {
|
|
16411
|
+
const rows = await this.list(projectId);
|
|
16412
|
+
const matches = rows.filter(
|
|
16413
|
+
(row) => row.key === key && (target === void 0 || row.target === target)
|
|
16414
|
+
);
|
|
16415
|
+
if (matches.length === 0) {
|
|
16416
|
+
throw new NaroError(
|
|
16417
|
+
"ENV_NOT_FOUND",
|
|
16418
|
+
target ? `No ${key} variable found for the ${target} environment.` : `No ${key} variable found.`,
|
|
16419
|
+
{ details: { key, ...target ? { target } : {} } }
|
|
16420
|
+
);
|
|
16421
|
+
}
|
|
16422
|
+
for (const row of matches) await this.removeById(projectId, row.id);
|
|
16423
|
+
return matches.length;
|
|
16424
|
+
}
|
|
16425
|
+
};
|
|
16426
|
+
var DomainsApi = class {
|
|
16427
|
+
constructor(client) {
|
|
16428
|
+
this.client = client;
|
|
16429
|
+
}
|
|
16430
|
+
async list(projectId) {
|
|
16431
|
+
const body = await this.client.request(
|
|
16432
|
+
`/api/projects/${encodeURIComponent(projectId)}/domains`,
|
|
16433
|
+
{ context: "List domains" }
|
|
16434
|
+
);
|
|
16435
|
+
return body.domains;
|
|
16436
|
+
}
|
|
16437
|
+
async add(projectId, hostname3) {
|
|
16438
|
+
const body = await this.client.request(
|
|
16439
|
+
`/api/projects/${encodeURIComponent(projectId)}/domains`,
|
|
16440
|
+
{ method: "POST", body: { hostname: hostname3 }, context: "Add domain" }
|
|
16441
|
+
);
|
|
16442
|
+
return body.domain;
|
|
16443
|
+
}
|
|
16444
|
+
async refresh(projectId, domainId) {
|
|
16445
|
+
const body = await this.client.request(
|
|
16446
|
+
`/api/projects/${encodeURIComponent(projectId)}/domains/${encodeURIComponent(domainId)}`,
|
|
16447
|
+
{ method: "PATCH", context: "Refresh domain" }
|
|
16448
|
+
);
|
|
16449
|
+
return body.domain;
|
|
16450
|
+
}
|
|
16451
|
+
async removeById(projectId, domainId) {
|
|
16452
|
+
await this.client.request(
|
|
16453
|
+
`/api/projects/${encodeURIComponent(projectId)}/domains/${encodeURIComponent(domainId)}`,
|
|
16454
|
+
{ method: "DELETE", context: "Remove domain" }
|
|
16455
|
+
);
|
|
16456
|
+
}
|
|
16457
|
+
/** Removes a domain by hostname. */
|
|
16458
|
+
async remove(projectId, hostname3) {
|
|
16459
|
+
const domains = await this.list(projectId);
|
|
16460
|
+
const match = domains.find((domain2) => domain2.hostname === hostname3);
|
|
16461
|
+
if (!match) {
|
|
16462
|
+
throw new NaroError(
|
|
16463
|
+
"DOMAIN_NOT_FOUND",
|
|
16464
|
+
`${hostname3} is not attached to this project.`,
|
|
16465
|
+
{ details: { hostname: hostname3 } }
|
|
16466
|
+
);
|
|
16467
|
+
}
|
|
16468
|
+
await this.removeById(projectId, match.id);
|
|
16469
|
+
}
|
|
16470
|
+
};
|
|
16471
|
+
var LogsApi = class {
|
|
16472
|
+
constructor(client) {
|
|
16473
|
+
this.client = client;
|
|
16474
|
+
}
|
|
16475
|
+
async runtime(projectId, options = {}) {
|
|
16476
|
+
const params = new URLSearchParams();
|
|
16477
|
+
params.set("env", options.environment ?? "production");
|
|
16478
|
+
if (options.level) params.set("level", options.level);
|
|
16479
|
+
const body = await this.client.request(
|
|
16480
|
+
`/api/projects/${encodeURIComponent(projectId)}/logs?${params.toString()}`,
|
|
16481
|
+
{ context: "Read runtime logs" }
|
|
16482
|
+
);
|
|
16483
|
+
return body.entries;
|
|
16484
|
+
}
|
|
16485
|
+
};
|
|
16486
|
+
|
|
16487
|
+
// ../../packages/sdk/src/dotenv.ts
|
|
16488
|
+
import { existsSync, readFileSync as readFileSync2 } from "fs";
|
|
16489
|
+
import { join as join10 } from "path";
|
|
16490
|
+
function parseDotenv(text) {
|
|
16491
|
+
const out = {};
|
|
16492
|
+
for (const line of text.split(/\r?\n/)) {
|
|
16493
|
+
const trimmed = line.trim();
|
|
16494
|
+
if (!trimmed || trimmed.startsWith("#")) continue;
|
|
16495
|
+
const eq = trimmed.indexOf("=");
|
|
16496
|
+
if (eq <= 0) continue;
|
|
16497
|
+
const key = trimmed.slice(0, eq).trim();
|
|
16498
|
+
let value = trimmed.slice(eq + 1).trim();
|
|
16499
|
+
if (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'"))
|
|
16500
|
+
value = value.slice(1, -1);
|
|
16501
|
+
out[key] = value;
|
|
16502
|
+
}
|
|
16503
|
+
return out;
|
|
15736
16504
|
}
|
|
15737
|
-
|
|
15738
|
-
const
|
|
15739
|
-
|
|
15740
|
-
|
|
15741
|
-
|
|
15742
|
-
}
|
|
15743
|
-
await client.auth.signOut();
|
|
16505
|
+
function readProjectEnv(dir) {
|
|
16506
|
+
for (const name of [".env.local", ".env.production", ".env"]) {
|
|
16507
|
+
const path = join10(dir, name);
|
|
16508
|
+
if (existsSync(path)) return parseDotenv(readFileSync2(path, "utf8"));
|
|
16509
|
+
}
|
|
16510
|
+
return {};
|
|
15744
16511
|
}
|
|
15745
|
-
|
|
15746
|
-
const
|
|
15747
|
-
|
|
15748
|
-
|
|
15749
|
-
|
|
15750
|
-
|
|
16512
|
+
function serializeDotenv(vars) {
|
|
16513
|
+
const needsQuotes = /[\s"'#$`\\]/;
|
|
16514
|
+
return `${Object.entries(vars).map(
|
|
16515
|
+
([key, value]) => needsQuotes.test(value) ? `${key}="${value.replaceAll("\\", "\\\\").replaceAll('"', '\\"')}"` : `${key}=${value}`
|
|
16516
|
+
).join("\n")}
|
|
16517
|
+
`;
|
|
16518
|
+
}
|
|
16519
|
+
|
|
16520
|
+
// ../../packages/sdk/src/framework.ts
|
|
16521
|
+
import { existsSync as existsSync2, readFileSync as readFileSync3 } from "fs";
|
|
16522
|
+
import { join as join11 } from "path";
|
|
16523
|
+
function readPackageJson2(dir) {
|
|
16524
|
+
try {
|
|
16525
|
+
return JSON.parse(
|
|
16526
|
+
readFileSync3(join11(dir, "package.json"), "utf8")
|
|
16527
|
+
);
|
|
16528
|
+
} catch {
|
|
16529
|
+
return null;
|
|
16530
|
+
}
|
|
16531
|
+
}
|
|
16532
|
+
function hasDependency(pkg, name) {
|
|
16533
|
+
return Boolean(pkg?.dependencies?.[name] ?? pkg?.devDependencies?.[name]);
|
|
16534
|
+
}
|
|
16535
|
+
function hasConfig(dir, base2, evidence) {
|
|
16536
|
+
for (const ext of ["js", "mjs", "cjs", "ts", "mts"]) {
|
|
16537
|
+
if (existsSync2(join11(dir, `${base2}.${ext}`))) {
|
|
16538
|
+
evidence.push(`${base2}.${ext}`);
|
|
16539
|
+
return true;
|
|
15751
16540
|
}
|
|
15752
16541
|
}
|
|
15753
|
-
|
|
15754
|
-
|
|
16542
|
+
return false;
|
|
16543
|
+
}
|
|
16544
|
+
function detectFramework2(dir) {
|
|
16545
|
+
const evidence = [];
|
|
16546
|
+
const pkg = readPackageJson2(dir);
|
|
16547
|
+
if (hasDependency(pkg, "next") || hasConfig(dir, "next.config", evidence)) {
|
|
16548
|
+
if (hasDependency(pkg, "next")) evidence.push("package.json: next");
|
|
16549
|
+
if (existsSync2(join11(dir, "app"))) evidence.push("app/");
|
|
16550
|
+
if (existsSync2(join11(dir, "pages"))) evidence.push("pages/");
|
|
16551
|
+
return {
|
|
16552
|
+
framework: "nextjs",
|
|
16553
|
+
name: "Next.js",
|
|
16554
|
+
buildCommand: "next build",
|
|
16555
|
+
outputDirectory: ".next",
|
|
16556
|
+
evidence,
|
|
16557
|
+
deployable: true
|
|
16558
|
+
};
|
|
16559
|
+
}
|
|
16560
|
+
if (hasDependency(pkg, "nuxt") || hasConfig(dir, "nuxt.config", evidence)) {
|
|
16561
|
+
if (hasDependency(pkg, "nuxt")) evidence.push("package.json: nuxt");
|
|
16562
|
+
return {
|
|
16563
|
+
framework: "nuxt",
|
|
16564
|
+
name: "Nuxt",
|
|
16565
|
+
buildCommand: "nuxt build",
|
|
16566
|
+
outputDirectory: ".output",
|
|
16567
|
+
evidence,
|
|
16568
|
+
deployable: false
|
|
16569
|
+
};
|
|
16570
|
+
}
|
|
16571
|
+
if (hasDependency(pkg, "astro") || hasConfig(dir, "astro.config", evidence)) {
|
|
16572
|
+
if (hasDependency(pkg, "astro")) evidence.push("package.json: astro");
|
|
16573
|
+
return {
|
|
16574
|
+
framework: "astro",
|
|
16575
|
+
name: "Astro",
|
|
16576
|
+
buildCommand: "astro build",
|
|
16577
|
+
outputDirectory: "dist",
|
|
16578
|
+
evidence,
|
|
16579
|
+
deployable: false
|
|
16580
|
+
};
|
|
16581
|
+
}
|
|
16582
|
+
if (hasDependency(pkg, "vite") || hasConfig(dir, "vite.config", evidence)) {
|
|
16583
|
+
if (hasDependency(pkg, "vite")) evidence.push("package.json: vite");
|
|
16584
|
+
return {
|
|
16585
|
+
framework: "vite",
|
|
16586
|
+
name: "Vite",
|
|
16587
|
+
buildCommand: "vite build",
|
|
16588
|
+
outputDirectory: "dist",
|
|
16589
|
+
evidence,
|
|
16590
|
+
deployable: true
|
|
16591
|
+
};
|
|
16592
|
+
}
|
|
16593
|
+
if (hasDependency(pkg, "react-scripts")) {
|
|
16594
|
+
evidence.push("package.json: react-scripts");
|
|
16595
|
+
return {
|
|
16596
|
+
framework: "react",
|
|
16597
|
+
name: "Create React App",
|
|
16598
|
+
buildCommand: "react-scripts build",
|
|
16599
|
+
outputDirectory: "build",
|
|
16600
|
+
evidence,
|
|
16601
|
+
deployable: true
|
|
16602
|
+
};
|
|
16603
|
+
}
|
|
16604
|
+
if (!pkg && existsSync2(join11(dir, "index.html"))) {
|
|
16605
|
+
evidence.push("index.html (no package.json)");
|
|
16606
|
+
return {
|
|
16607
|
+
framework: "static",
|
|
16608
|
+
name: "Static site",
|
|
16609
|
+
buildCommand: null,
|
|
16610
|
+
outputDirectory: ".",
|
|
16611
|
+
evidence,
|
|
16612
|
+
deployable: true
|
|
16613
|
+
};
|
|
16614
|
+
}
|
|
16615
|
+
return {
|
|
16616
|
+
framework: "unknown",
|
|
16617
|
+
name: "Unknown",
|
|
16618
|
+
buildCommand: null,
|
|
16619
|
+
outputDirectory: null,
|
|
16620
|
+
evidence,
|
|
16621
|
+
deployable: false
|
|
16622
|
+
};
|
|
16623
|
+
}
|
|
16624
|
+
|
|
16625
|
+
// ../../packages/sdk/src/link.ts
|
|
16626
|
+
import {
|
|
16627
|
+
existsSync as existsSync3,
|
|
16628
|
+
mkdirSync as mkdirSync2,
|
|
16629
|
+
readFileSync as readFileSync4,
|
|
16630
|
+
rmSync as rmSync2,
|
|
16631
|
+
writeFileSync as writeFileSync2
|
|
16632
|
+
} from "fs";
|
|
16633
|
+
import { join as join12 } from "path";
|
|
16634
|
+
function linkDir(dir) {
|
|
16635
|
+
return join12(dir, ".naro");
|
|
16636
|
+
}
|
|
16637
|
+
function linkFile(dir) {
|
|
16638
|
+
return join12(linkDir(dir), "project.json");
|
|
16639
|
+
}
|
|
16640
|
+
function readProjectLink(dir) {
|
|
16641
|
+
try {
|
|
16642
|
+
const parsed = JSON.parse(readFileSync4(linkFile(dir), "utf8"));
|
|
16643
|
+
if (typeof parsed.projectId !== "string" || parsed.projectId.length === 0)
|
|
16644
|
+
return null;
|
|
16645
|
+
return {
|
|
16646
|
+
projectId: parsed.projectId,
|
|
16647
|
+
...typeof parsed.teamSlug === "string" ? { teamSlug: parsed.teamSlug } : {},
|
|
16648
|
+
...typeof parsed.projectName === "string" ? { projectName: parsed.projectName } : {}
|
|
16649
|
+
};
|
|
16650
|
+
} catch {
|
|
16651
|
+
return null;
|
|
16652
|
+
}
|
|
16653
|
+
}
|
|
16654
|
+
function writeProjectLink(dir, link) {
|
|
16655
|
+
mkdirSync2(linkDir(dir), { recursive: true });
|
|
16656
|
+
writeFileSync2(linkFile(dir), `${JSON.stringify(link, null, 2)}
|
|
16657
|
+
`);
|
|
16658
|
+
writeFileSync2(
|
|
16659
|
+
join12(linkDir(dir), "README.txt"),
|
|
16660
|
+
"This directory links the project to Naro and should not be committed.\n"
|
|
16661
|
+
);
|
|
16662
|
+
}
|
|
16663
|
+
function removeProjectLink(dir) {
|
|
16664
|
+
if (!existsSync3(linkDir(dir))) return false;
|
|
16665
|
+
rmSync2(linkDir(dir), { recursive: true, force: true });
|
|
16666
|
+
return true;
|
|
16667
|
+
}
|
|
16668
|
+
function ensureGitignoreHasNaro(dir) {
|
|
16669
|
+
const gitignorePath = join12(dir, ".gitignore");
|
|
16670
|
+
const hasGitignore = existsSync3(gitignorePath);
|
|
16671
|
+
if (!hasGitignore && !existsSync3(join12(dir, ".git"))) return false;
|
|
16672
|
+
const current = hasGitignore ? readFileSync4(gitignorePath, "utf8") : "";
|
|
16673
|
+
const covered = current.split(/\r?\n/).map((line) => line.trim()).some((line) => line === ".naro" || line === ".naro/" || line === "/.naro");
|
|
16674
|
+
if (covered) return false;
|
|
16675
|
+
const suffix = current.length === 0 || current.endsWith("\n") ? "" : "\n";
|
|
16676
|
+
writeFileSync2(gitignorePath, `${current}${suffix}.naro
|
|
16677
|
+
`);
|
|
16678
|
+
return true;
|
|
16679
|
+
}
|
|
16680
|
+
|
|
16681
|
+
// src/git-info.ts
|
|
16682
|
+
import { execFile } from "child_process";
|
|
16683
|
+
var SUPPORTED_REMOTE = [
|
|
16684
|
+
/^https:\/\/[^\s]+$/,
|
|
16685
|
+
/^ssh:\/\/[^\s]+$/,
|
|
16686
|
+
/^git@[A-Za-z0-9.-]+:[A-Za-z0-9._/-]+(\.git)?$/
|
|
16687
|
+
];
|
|
16688
|
+
var defaultExec = (args, cwd) => new Promise((resolve4, reject) => {
|
|
16689
|
+
execFile("git", args, { cwd }, (error51, stdout) => {
|
|
16690
|
+
if (error51) reject(error51);
|
|
16691
|
+
else resolve4(stdout);
|
|
16692
|
+
});
|
|
16693
|
+
});
|
|
16694
|
+
async function tryExec(exec, args, cwd) {
|
|
16695
|
+
try {
|
|
16696
|
+
const out = (await exec(args, cwd)).trim();
|
|
16697
|
+
return out.length > 0 ? out : null;
|
|
16698
|
+
} catch {
|
|
16699
|
+
return null;
|
|
16700
|
+
}
|
|
16701
|
+
}
|
|
16702
|
+
async function detectGitInfo(dir, exec = defaultExec) {
|
|
16703
|
+
const remote = await tryExec(exec, ["remote", "get-url", "origin"], dir);
|
|
16704
|
+
const branch = await tryExec(
|
|
16705
|
+
exec,
|
|
16706
|
+
["rev-parse", "--abbrev-ref", "HEAD"],
|
|
16707
|
+
dir
|
|
16708
|
+
);
|
|
16709
|
+
return {
|
|
16710
|
+
repositoryUrl: remote !== null && SUPPORTED_REMOTE.some((re) => re.test(remote)) ? remote : null,
|
|
16711
|
+
branch: branch !== null && branch !== "HEAD" ? branch : null
|
|
16712
|
+
};
|
|
15755
16713
|
}
|
|
15756
16714
|
|
|
15757
16715
|
// src/upload.ts
|
|
@@ -15769,12 +16727,52 @@ async function authed(opts, path, init) {
|
|
|
15769
16727
|
);
|
|
15770
16728
|
return response;
|
|
15771
16729
|
}
|
|
15772
|
-
|
|
15773
|
-
|
|
15774
|
-
|
|
15775
|
-
|
|
15776
|
-
|
|
15777
|
-
|
|
16730
|
+
function uploadPorts(opts) {
|
|
16731
|
+
const doFetch = opts.fetch ?? fetch;
|
|
16732
|
+
return {
|
|
16733
|
+
resolvePutUrls: async (entries) => {
|
|
16734
|
+
try {
|
|
16735
|
+
const response = await authed(opts, "/artifacts/urls", {
|
|
16736
|
+
method: "POST",
|
|
16737
|
+
headers: { "Content-Type": "application/json" },
|
|
16738
|
+
body: JSON.stringify({
|
|
16739
|
+
files: entries.map((entry) => ({
|
|
16740
|
+
kind: entry.kind,
|
|
16741
|
+
name: entry.file.name
|
|
16742
|
+
}))
|
|
16743
|
+
})
|
|
16744
|
+
});
|
|
16745
|
+
return parseArtifactPutUrls(await response.json());
|
|
16746
|
+
} catch {
|
|
16747
|
+
return null;
|
|
16748
|
+
}
|
|
16749
|
+
},
|
|
16750
|
+
putSigned: async (url2, entry) => {
|
|
16751
|
+
const response = await doFetch(url2, {
|
|
16752
|
+
method: "PUT",
|
|
16753
|
+
headers: { "Content-Type": entry.file.contentType },
|
|
16754
|
+
body: entry.file.data
|
|
16755
|
+
});
|
|
16756
|
+
if (!response.ok)
|
|
16757
|
+
throw new Error(
|
|
16758
|
+
`Artifact upload returned ${String(response.status)} for ${entry.file.name}`
|
|
16759
|
+
);
|
|
16760
|
+
},
|
|
16761
|
+
putProxied: async (entry) => {
|
|
16762
|
+
await authed(
|
|
16763
|
+
opts,
|
|
16764
|
+
`/artifacts/${entry.kind}/${encodeURIComponent(entry.file.name)}`,
|
|
16765
|
+
{
|
|
16766
|
+
method: "PUT",
|
|
16767
|
+
headers: {
|
|
16768
|
+
"Content-Type": entry.file.contentType,
|
|
16769
|
+
"X-Artifact-Name": entry.file.name
|
|
16770
|
+
},
|
|
16771
|
+
body: entry.file.data
|
|
16772
|
+
}
|
|
16773
|
+
);
|
|
16774
|
+
}
|
|
16775
|
+
};
|
|
15778
16776
|
}
|
|
15779
16777
|
function manifest(artifact) {
|
|
15780
16778
|
const meta3 = ({ name, contentType: contentType2, data }) => ({
|
|
@@ -15787,16 +16785,18 @@ function manifest(artifact) {
|
|
|
15787
16785
|
projectId: artifact.projectId,
|
|
15788
16786
|
entrypoint: artifact.entrypoint.name,
|
|
15789
16787
|
modules: [artifact.entrypoint, ...artifact.modules].map(meta3),
|
|
15790
|
-
assets: artifact.assets.map(
|
|
16788
|
+
assets: artifact.assets.map((file2) => ({
|
|
16789
|
+
...meta3(file2),
|
|
16790
|
+
hash: hashAsset(file2.data)
|
|
16791
|
+
})),
|
|
15791
16792
|
compatibilityDate: artifact.compatibilityDate,
|
|
15792
16793
|
compatibilityFlags: artifact.compatibilityFlags,
|
|
15793
|
-
assetsBinding: artifact.assetsBinding
|
|
16794
|
+
assetsBinding: artifact.assetsBinding,
|
|
16795
|
+
...artifact.assetsConfig ? { assetsConfig: artifact.assetsConfig } : {}
|
|
15794
16796
|
};
|
|
15795
16797
|
}
|
|
15796
16798
|
async function uploadArtifact(artifact, opts) {
|
|
15797
|
-
|
|
15798
|
-
await put(opts, "module", file2);
|
|
15799
|
-
for (const file2 of artifact.assets) await put(opts, "asset", file2);
|
|
16799
|
+
await uploadArtifactFiles(artifact, uploadPorts(opts));
|
|
15800
16800
|
const response = await authed(opts, "/artifacts/complete", {
|
|
15801
16801
|
method: "POST",
|
|
15802
16802
|
headers: { "Content-Type": "application/json" },
|
|
@@ -15812,59 +16812,11 @@ async function uploadArtifact(artifact, opts) {
|
|
|
15812
16812
|
}
|
|
15813
16813
|
|
|
15814
16814
|
// src/deploy.ts
|
|
15815
|
-
|
|
15816
|
-
|
|
15817
|
-
const response = await doFetch(
|
|
15818
|
-
`${opts.controlPlaneUrl.replace(/\/$/, "")}/api/cli/deployments`,
|
|
15819
|
-
{
|
|
15820
|
-
method: "POST",
|
|
15821
|
-
headers: {
|
|
15822
|
-
"Content-Type": "application/json",
|
|
15823
|
-
Authorization: `Bearer ${accessToken}`
|
|
15824
|
-
},
|
|
15825
|
-
body: JSON.stringify({
|
|
15826
|
-
name: opts.name,
|
|
15827
|
-
production: opts.production,
|
|
15828
|
-
...opts.repositoryUrl != null && { repositoryUrl: opts.repositoryUrl },
|
|
15829
|
-
...opts.branch != null && { branch: opts.branch },
|
|
15830
|
-
...opts.rootDirectory != null && { rootDirectory: opts.rootDirectory },
|
|
15831
|
-
...opts.env !== void 0 && { env: opts.env }
|
|
15832
|
-
})
|
|
15833
|
-
}
|
|
15834
|
-
);
|
|
15835
|
-
if (!response.ok) {
|
|
15836
|
-
if (response.status === 401)
|
|
15837
|
-
throw new Error("Session expired or invalid. Run `naro login`.");
|
|
15838
|
-
throw new Error(
|
|
15839
|
-
`Could not start deployment: control plane returned ${String(response.status)}.`
|
|
15840
|
-
);
|
|
15841
|
-
}
|
|
15842
|
-
return await response.json();
|
|
16815
|
+
function parseDotenv2(text) {
|
|
16816
|
+
return parseDotenv(text);
|
|
15843
16817
|
}
|
|
15844
|
-
function
|
|
15845
|
-
return (
|
|
15846
|
-
}
|
|
15847
|
-
async function requireSessionOrLogin(log2, home = homedir3(), loginFlow = () => login({
|
|
15848
|
-
openBrowser: (url2) => {
|
|
15849
|
-
log2(`If your browser did not open, visit:
|
|
15850
|
-
${url2}`);
|
|
15851
|
-
openBrowser(url2);
|
|
15852
|
-
}
|
|
15853
|
-
})) {
|
|
15854
|
-
const stored = loadStoredSession(home);
|
|
15855
|
-
if (stored) {
|
|
15856
|
-
if (!isExpired(stored)) return stored;
|
|
15857
|
-
try {
|
|
15858
|
-
const refreshed = await refreshStoredSession(stored.refresh_token);
|
|
15859
|
-
saveSession(refreshed, home);
|
|
15860
|
-
return refreshed;
|
|
15861
|
-
} catch {
|
|
15862
|
-
}
|
|
15863
|
-
}
|
|
15864
|
-
log2("Not logged in \u2014 opening your browser to sign in with GitHub\u2026");
|
|
15865
|
-
const session = await loginFlow();
|
|
15866
|
-
saveSession(session, home);
|
|
15867
|
-
return session;
|
|
16818
|
+
function readProjectEnv2(dir) {
|
|
16819
|
+
return readProjectEnv(dir);
|
|
15868
16820
|
}
|
|
15869
16821
|
function eventsObserver(cp2, deploymentId, token, fetchImpl = fetch) {
|
|
15870
16822
|
const post = (body) => fetchImpl(`${cp2}/api/internal/deployments/${deploymentId}/events`, {
|
|
@@ -15876,36 +16828,28 @@ function eventsObserver(cp2, deploymentId, token, fetchImpl = fetch) {
|
|
|
15876
16828
|
body: JSON.stringify(body)
|
|
15877
16829
|
}).then(() => {
|
|
15878
16830
|
});
|
|
16831
|
+
const postStatus = async (body) => {
|
|
16832
|
+
let lastError;
|
|
16833
|
+
for (let attempt = 0; attempt < 3; attempt++) {
|
|
16834
|
+
try {
|
|
16835
|
+
return await post(body);
|
|
16836
|
+
} catch (error51) {
|
|
16837
|
+
lastError = error51;
|
|
16838
|
+
await new Promise(
|
|
16839
|
+
(resolve4) => setTimeout(resolve4, 500 * (attempt + 1))
|
|
16840
|
+
);
|
|
16841
|
+
}
|
|
16842
|
+
}
|
|
16843
|
+
throw lastError;
|
|
16844
|
+
};
|
|
15879
16845
|
return {
|
|
15880
|
-
onStatus: (status) =>
|
|
16846
|
+
onStatus: (status) => postStatus({ type: "status", status }),
|
|
15881
16847
|
onLog: (log2) => post({ type: "log", log: log2 }).catch(() => {
|
|
15882
16848
|
}),
|
|
15883
16849
|
onMetadata: (metadata) => post({ type: "metadata", metadata }).catch(() => {
|
|
15884
16850
|
})
|
|
15885
16851
|
};
|
|
15886
16852
|
}
|
|
15887
|
-
function parseDotenv(text) {
|
|
15888
|
-
const out = {};
|
|
15889
|
-
for (const line of text.split(/\r?\n/)) {
|
|
15890
|
-
const trimmed = line.trim();
|
|
15891
|
-
if (!trimmed || trimmed.startsWith("#")) continue;
|
|
15892
|
-
const eq = trimmed.indexOf("=");
|
|
15893
|
-
if (eq <= 0) continue;
|
|
15894
|
-
const key = trimmed.slice(0, eq).trim();
|
|
15895
|
-
let value = trimmed.slice(eq + 1).trim();
|
|
15896
|
-
if (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'"))
|
|
15897
|
-
value = value.slice(1, -1);
|
|
15898
|
-
out[key] = value;
|
|
15899
|
-
}
|
|
15900
|
-
return out;
|
|
15901
|
-
}
|
|
15902
|
-
function readProjectEnv(dir) {
|
|
15903
|
-
for (const name of [".env.local", ".env.production", ".env"]) {
|
|
15904
|
-
const path = join9(dir, name);
|
|
15905
|
-
if (existsSync(path)) return parseDotenv(readFileSync2(path, "utf8"));
|
|
15906
|
-
}
|
|
15907
|
-
return {};
|
|
15908
|
-
}
|
|
15909
16853
|
function slugify2(value) {
|
|
15910
16854
|
const slug = value.replace(/^@[^/]+\//, "").toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 54);
|
|
15911
16855
|
return slug || "app";
|
|
@@ -15913,19 +16857,19 @@ function slugify2(value) {
|
|
|
15913
16857
|
async function projectName(sourceDir) {
|
|
15914
16858
|
try {
|
|
15915
16859
|
const pkg = JSON.parse(
|
|
15916
|
-
await
|
|
16860
|
+
await readFile7(join13(sourceDir, "package.json"), "utf8")
|
|
15917
16861
|
);
|
|
15918
16862
|
if (pkg.name) return pkg.name;
|
|
15919
16863
|
} catch {
|
|
15920
16864
|
}
|
|
15921
16865
|
return basename(sourceDir);
|
|
15922
16866
|
}
|
|
15923
|
-
var COPY_SKIP = /(^|[\\/])(node_modules|\.next|\.open-next|\.git|\.wrangler|\.turbo)([\\/]|$)/;
|
|
16867
|
+
var COPY_SKIP = /(^|[\\/])(node_modules|\.next|\.open-next|\.git|\.wrangler|\.turbo|\.naro)([\\/]|$)/;
|
|
15924
16868
|
function resolveBin(name) {
|
|
15925
16869
|
let dir = dirname(fileURLToPath(import.meta.url));
|
|
15926
16870
|
for (; ; ) {
|
|
15927
|
-
const candidate =
|
|
15928
|
-
if (
|
|
16871
|
+
const candidate = join13(dir, "node_modules", ".bin", name);
|
|
16872
|
+
if (existsSync4(candidate)) return candidate;
|
|
15929
16873
|
const parent = dirname(dir);
|
|
15930
16874
|
if (parent === dir) return name;
|
|
15931
16875
|
dir = parent;
|
|
@@ -15949,7 +16893,7 @@ async function detectPackageManagerLenient(root) {
|
|
|
15949
16893
|
}
|
|
15950
16894
|
function localDependencies(sourceDir) {
|
|
15951
16895
|
return {
|
|
15952
|
-
createWorkspace: () => mkdtemp2(
|
|
16896
|
+
createWorkspace: () => mkdtemp2(join13(tmpdir2(), "naro-build-")),
|
|
15953
16897
|
removeWorkspace: (path) => rm2(path, { recursive: true, force: true }),
|
|
15954
16898
|
async runCommand(spec, observer) {
|
|
15955
16899
|
if (spec.command === "git" && spec.args[0] === "clone") {
|
|
@@ -15964,11 +16908,16 @@ function localDependencies(sourceDir) {
|
|
|
15964
16908
|
if (spec.command === "git" && spec.args[0] === "rev-parse") {
|
|
15965
16909
|
return { stdout: ["0".repeat(40)], stderr: [] };
|
|
15966
16910
|
}
|
|
16911
|
+
if (spec.command === "git" && spec.args[0] === "log") {
|
|
16912
|
+
return { stdout: [], stderr: [] };
|
|
16913
|
+
}
|
|
15967
16914
|
return runCommand(spec, observer);
|
|
15968
16915
|
},
|
|
15969
16916
|
detectPackageManager: detectPackageManagerLenient,
|
|
15970
16917
|
resolveAppRoot,
|
|
15971
|
-
|
|
16918
|
+
detectFramework,
|
|
16919
|
+
readStaticArtifact,
|
|
16920
|
+
createPackageManagerConfig,
|
|
15972
16921
|
createPlatformBuildConfig,
|
|
15973
16922
|
readOpenNextArtifact,
|
|
15974
16923
|
openNextCommand: resolveBin("opennextjs-cloudflare"),
|
|
@@ -15976,61 +16925,142 @@ function localDependencies(sourceDir) {
|
|
|
15976
16925
|
};
|
|
15977
16926
|
}
|
|
15978
16927
|
async function deployDirectory(sourceDir, options, log2) {
|
|
15979
|
-
const
|
|
15980
|
-
const session = await requireSessionOrLogin(log2);
|
|
15981
|
-
const name = slugify2(options.name ?? await projectName(sourceDir));
|
|
16928
|
+
const cpUrl = controlPlaneUrl();
|
|
15982
16929
|
const production = options.production ?? false;
|
|
16930
|
+
const framework = detectFramework2(sourceDir);
|
|
16931
|
+
if (!framework.deployable && !options.force) {
|
|
16932
|
+
throw new NaroError(
|
|
16933
|
+
"FRAMEWORK_UNSUPPORTED",
|
|
16934
|
+
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.`,
|
|
16935
|
+
{ details: { framework: framework.framework } }
|
|
16936
|
+
);
|
|
16937
|
+
}
|
|
16938
|
+
log2(`\u25B8 detected ${framework.name}`);
|
|
16939
|
+
const auth = await resolveAuth({
|
|
16940
|
+
explicitToken: options.token,
|
|
16941
|
+
interactiveLogin: options.allowLogin === false ? false : {
|
|
16942
|
+
openBrowser,
|
|
16943
|
+
log: log2
|
|
16944
|
+
}
|
|
16945
|
+
});
|
|
16946
|
+
const client = new NaroClient({
|
|
16947
|
+
baseUrl: cpUrl,
|
|
16948
|
+
getAccessToken: () => auth.getAccessToken(),
|
|
16949
|
+
...options.fetch ? { fetch: options.fetch } : {}
|
|
16950
|
+
});
|
|
16951
|
+
const name = slugify2(options.name ?? await projectName(sourceDir));
|
|
15983
16952
|
const git = await detectGitInfo(sourceDir);
|
|
15984
16953
|
const branch = options.branch ?? git.branch;
|
|
15985
|
-
if (
|
|
16954
|
+
if (options.projectId) log2(`\u25B8 linked project ${options.projectId}`);
|
|
16955
|
+
else if (git.repositoryUrl) log2(`\u25B8 linking ${git.repositoryUrl}`);
|
|
15986
16956
|
if (options.env && Object.keys(options.env).length > 0)
|
|
15987
|
-
log2(`\u25B8 syncing ${Object.keys(options.env).length} env var(s)`);
|
|
16957
|
+
log2(`\u25B8 syncing ${String(Object.keys(options.env).length)} env var(s)`);
|
|
15988
16958
|
log2("\u25B8 starting deployment");
|
|
15989
|
-
const ctx = await
|
|
15990
|
-
name,
|
|
16959
|
+
const ctx = await client.deployments.createCli({
|
|
16960
|
+
...options.projectId ? { projectId: options.projectId } : { name },
|
|
15991
16961
|
production,
|
|
15992
|
-
|
|
15993
|
-
|
|
15994
|
-
|
|
15995
|
-
|
|
15996
|
-
|
|
16962
|
+
...git.repositoryUrl != null && { repositoryUrl: git.repositoryUrl },
|
|
16963
|
+
...branch != null && { branch },
|
|
16964
|
+
...options.rootDirectory != null && {
|
|
16965
|
+
rootDirectory: options.rootDirectory
|
|
16966
|
+
},
|
|
16967
|
+
...options.env !== void 0 && { env: options.env }
|
|
15997
16968
|
});
|
|
15998
16969
|
if (ctx.envSkipped?.length)
|
|
15999
16970
|
log2(`\u26A0 skipped env: ${ctx.envSkipped.join(", ")}`);
|
|
16000
|
-
const executor = new LocalBuildExecutor(localDependencies(sourceDir));
|
|
16001
|
-
const observer = eventsObserver(
|
|
16002
|
-
|
|
16003
|
-
|
|
16004
|
-
|
|
16005
|
-
|
|
16006
|
-
repositoryUrl: sourceDir,
|
|
16007
|
-
branch: ctx.branch,
|
|
16008
|
-
rootDirectory: options.rootDirectory ?? ctx.rootDirectory,
|
|
16009
|
-
env: { ...ctx.env, ...options.env ?? {} }
|
|
16010
|
-
},
|
|
16011
|
-
observer
|
|
16971
|
+
const executor = options.executor ?? new LocalBuildExecutor(localDependencies(sourceDir));
|
|
16972
|
+
const observer = eventsObserver(
|
|
16973
|
+
cpUrl,
|
|
16974
|
+
ctx.deploymentId,
|
|
16975
|
+
ctx.callbackToken,
|
|
16976
|
+
options.fetch
|
|
16012
16977
|
);
|
|
16013
|
-
|
|
16014
|
-
|
|
16978
|
+
let artifact;
|
|
16979
|
+
try {
|
|
16980
|
+
const result = await executor.build(
|
|
16981
|
+
{
|
|
16982
|
+
deploymentId: ctx.deploymentId,
|
|
16983
|
+
projectId: ctx.projectId,
|
|
16984
|
+
repositoryUrl: sourceDir,
|
|
16985
|
+
branch: ctx.branch,
|
|
16986
|
+
rootDirectory: options.rootDirectory ?? ctx.rootDirectory,
|
|
16987
|
+
env: { ...ctx.env, ...options.env ?? {} }
|
|
16988
|
+
},
|
|
16989
|
+
observer
|
|
16990
|
+
);
|
|
16991
|
+
artifact = result.artifact;
|
|
16992
|
+
} catch (error51) {
|
|
16993
|
+
throw new NaroError(
|
|
16994
|
+
"BUILD_FAILED",
|
|
16995
|
+
error51 instanceof Error ? error51.message : "The local build failed.",
|
|
16996
|
+
{ details: { deploymentId: ctx.deploymentId }, cause: error51 }
|
|
16997
|
+
);
|
|
16998
|
+
}
|
|
16999
|
+
await uploadArtifact(artifact, {
|
|
17000
|
+
controlPlaneUrl: cpUrl,
|
|
16015
17001
|
deploymentId: ctx.deploymentId,
|
|
16016
|
-
callbackToken: ctx.callbackToken
|
|
17002
|
+
callbackToken: ctx.callbackToken,
|
|
17003
|
+
...options.fetch ? { fetch: options.fetch } : {}
|
|
16017
17004
|
});
|
|
17005
|
+
log2("\u25B8 publishing worker");
|
|
17006
|
+
const detail = await waitForFinish(client, ctx, log2);
|
|
17007
|
+
const url2 = deploymentUrl(detail, ctx, production);
|
|
17008
|
+
if (detail.deployment.status === "FAILED") {
|
|
17009
|
+
throw new NaroError(
|
|
17010
|
+
detail.deployment.errorCode ?? "DEPLOY_FAILED",
|
|
17011
|
+
detail.deployment.errorMessage ?? "The deployment failed.",
|
|
17012
|
+
{ details: { deploymentId: ctx.deploymentId } }
|
|
17013
|
+
);
|
|
17014
|
+
}
|
|
16018
17015
|
return {
|
|
16019
|
-
|
|
16020
|
-
|
|
17016
|
+
deploymentId: ctx.deploymentId,
|
|
17017
|
+
projectId: ctx.projectId,
|
|
17018
|
+
status: detail.deployment.status,
|
|
17019
|
+
url: url2,
|
|
17020
|
+
production
|
|
16021
17021
|
};
|
|
16022
17022
|
}
|
|
17023
|
+
async function waitForFinish(client, ctx, log2) {
|
|
17024
|
+
return client.deployments.wait(ctx.deploymentId, {
|
|
17025
|
+
intervalMs: 2500,
|
|
17026
|
+
timeoutMs: 10 * 6e4,
|
|
17027
|
+
onStatus: (status) => {
|
|
17028
|
+
if (status === "READY") log2("\u2713 worker deployed");
|
|
17029
|
+
}
|
|
17030
|
+
});
|
|
17031
|
+
}
|
|
17032
|
+
function deploymentUrl(detail, ctx, production) {
|
|
17033
|
+
if (detail.deployment.url) return detail.deployment.url;
|
|
17034
|
+
const surface = ctx.subdomain ?? ctx.projectId;
|
|
17035
|
+
const host = new URL(ctx.controlPlaneUrl || controlPlaneUrl()).hostname;
|
|
17036
|
+
return `https://${surface}${production ? "" : "-preview"}.${host}`;
|
|
17037
|
+
}
|
|
16023
17038
|
|
|
16024
17039
|
export {
|
|
16025
|
-
|
|
16026
|
-
|
|
16027
|
-
|
|
16028
|
-
|
|
17040
|
+
NaroError,
|
|
17041
|
+
isNaroError,
|
|
17042
|
+
toNaroError,
|
|
17043
|
+
saveSession,
|
|
17044
|
+
loadStoredSession,
|
|
17045
|
+
clearSession,
|
|
17046
|
+
isExpired,
|
|
16029
17047
|
controlPlaneUrl,
|
|
16030
|
-
|
|
16031
|
-
|
|
16032
|
-
|
|
17048
|
+
createAuthClient,
|
|
17049
|
+
refreshStoredSession,
|
|
17050
|
+
login,
|
|
17051
|
+
resolveAuth,
|
|
17052
|
+
openBrowser,
|
|
17053
|
+
isTerminalStatus,
|
|
17054
|
+
NaroClient,
|
|
16033
17055
|
readProjectEnv,
|
|
17056
|
+
serializeDotenv,
|
|
17057
|
+
readProjectLink,
|
|
17058
|
+
writeProjectLink,
|
|
17059
|
+
removeProjectLink,
|
|
17060
|
+
ensureGitignoreHasNaro,
|
|
17061
|
+
parseDotenv2 as parseDotenv,
|
|
17062
|
+
readProjectEnv2,
|
|
17063
|
+
eventsObserver,
|
|
16034
17064
|
slugify2 as slugify,
|
|
16035
17065
|
deployDirectory
|
|
16036
17066
|
};
|