nuxt-files-sdk 0.1.0 → 0.2.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.
Files changed (94) hide show
  1. package/dist/config/evaluate.js +149 -0
  2. package/dist/config/load.js +22 -40
  3. package/dist/config/merge.d.ts +1 -2
  4. package/dist/config/merge.js +0 -2
  5. package/dist/config/prepare.js +58 -0
  6. package/dist/config/prune.js +14 -13
  7. package/dist/config.d.ts +11 -6
  8. package/dist/config.js +0 -2
  9. package/dist/devtools/auth.js +0 -2
  10. package/dist/devtools/client/app.js +2 -2
  11. package/dist/devtools/client/index.html +4 -0
  12. package/dist/devtools/devframe.js +1 -3
  13. package/dist/devtools/diagnostics.d.ts +1 -2
  14. package/dist/devtools/diagnostics.js +0 -2
  15. package/dist/devtools/enabled.d.ts +1 -2
  16. package/dist/devtools/enabled.js +0 -2
  17. package/dist/devtools/files-read.d.ts +2 -3
  18. package/dist/devtools/files-read.js +0 -2
  19. package/dist/devtools/files-write.d.ts +2 -3
  20. package/dist/devtools/files-write.js +0 -2
  21. package/dist/devtools/files.js +7 -10
  22. package/dist/devtools/index.js +24 -26
  23. package/dist/devtools/nuxt-v3-handler.d.ts +1 -2
  24. package/dist/devtools/nuxt-v3-handler.js +0 -2
  25. package/dist/devtools/nuxt-v3.js +1 -3
  26. package/dist/devtools/nuxt-v4.js +4 -7
  27. package/dist/devtools/paths.js +8 -0
  28. package/dist/devtools/snapshot.d.ts +5 -14
  29. package/dist/devtools/snapshot.js +2 -9
  30. package/dist/devtools/token.d.ts +1 -2
  31. package/dist/devtools/token.js +0 -2
  32. package/dist/integration/capabilities.js +17 -0
  33. package/dist/integration/dependencies.d.ts +12 -0
  34. package/dist/integration/dependencies.js +67 -0
  35. package/dist/integration/diagnostics.d.ts +9 -0
  36. package/dist/integration/diagnostics.js +80 -0
  37. package/dist/integration/imports.js +83 -0
  38. package/dist/integration/nitro-dev-close.js +13 -0
  39. package/dist/integration/nitro-event.d.ts +7 -0
  40. package/dist/integration/nitro-event.js +24 -0
  41. package/dist/integration/nitro.d.ts +9 -2
  42. package/dist/integration/nitro.js +171 -92
  43. package/dist/integration/nuxt-diagnostics.js +23 -0
  44. package/dist/integration/nuxt.js +235 -0
  45. package/dist/integration/resolve.d.ts +1 -0
  46. package/dist/integration/resolve.js +113 -0
  47. package/dist/integration/terminal.js +20 -0
  48. package/dist/integration/update.js +59 -0
  49. package/dist/meta.js +8 -0
  50. package/dist/module.d.ts +1 -8
  51. package/dist/module.js +74 -59
  52. package/dist/module.json +8 -0
  53. package/dist/nitro.d.ts +1 -2
  54. package/dist/nitro.js +7 -7
  55. package/dist/package.js +1 -3
  56. package/dist/runtime/development.d.ts +5 -0
  57. package/dist/runtime/development.js +14 -0
  58. package/dist/runtime/gateway-secret.d.ts +4 -0
  59. package/dist/runtime/gateway-secret.js +10 -0
  60. package/dist/runtime/internal.d.ts +1 -2
  61. package/dist/runtime/internal.js +1 -2
  62. package/dist/runtime/normalize.d.ts +1 -0
  63. package/dist/runtime/normalize.js +0 -2
  64. package/dist/runtime/provider-types.d.ts +1 -2
  65. package/dist/runtime/registry.d.ts +9 -22
  66. package/dist/runtime/registry.js +23 -28
  67. package/dist/runtime.d.ts +1 -2
  68. package/dist/runtime.js +0 -2
  69. package/package.json +248 -14
  70. package/dist/config/load.js.map +0 -1
  71. package/dist/config/merge.js.map +0 -1
  72. package/dist/config/prune.js.map +0 -1
  73. package/dist/config.js.map +0 -1
  74. package/dist/devtools/auth.js.map +0 -1
  75. package/dist/devtools/devframe.js.map +0 -1
  76. package/dist/devtools/diagnostics.js.map +0 -1
  77. package/dist/devtools/enabled.js.map +0 -1
  78. package/dist/devtools/files-read.js.map +0 -1
  79. package/dist/devtools/files-write.js.map +0 -1
  80. package/dist/devtools/files.js.map +0 -1
  81. package/dist/devtools/index.js.map +0 -1
  82. package/dist/devtools/nuxt-v3-handler.js.map +0 -1
  83. package/dist/devtools/nuxt-v3.js.map +0 -1
  84. package/dist/devtools/nuxt-v4.js.map +0 -1
  85. package/dist/devtools/snapshot.js.map +0 -1
  86. package/dist/devtools/token.js.map +0 -1
  87. package/dist/integration/nitro.js.map +0 -1
  88. package/dist/module.js.map +0 -1
  89. package/dist/nitro.js.map +0 -1
  90. package/dist/package.js.map +0 -1
  91. package/dist/runtime/internal.js.map +0 -1
  92. package/dist/runtime/normalize.js.map +0 -1
  93. package/dist/runtime/registry.js.map +0 -1
  94. package/dist/runtime.js.map +0 -1
@@ -1,11 +1,14 @@
1
- import { normalizeFilesConfig } from "../runtime/normalize.js";
2
- import { loadFilesConfig } from "../config/load.js";
3
- import { pruneFilesConfigSource } from "../config/prune.js";
4
- import { resolve } from "node:path";
5
- import { mkdir, readFile, writeFile } from "node:fs/promises";
6
- import { createRequire } from "node:module";
7
- import { fileURLToPath } from "node:url";
8
- import { getProvider, listEnvVars } from "files-sdk/providers";
1
+ import { fileHash } from "../runtime/development.js";
2
+ import { registerSdkAliases, resolveOwnedSdk, resolvePackage, sdkTypePaths } from "./resolve.js";
3
+ import { prepareFilesConfig } from "../config/prepare.js";
4
+ import { deploymentTarget, storageDependencies } from "./dependencies.js";
5
+ import { dependencySession, diagnoseDependencies, reportDependencyIssues } from "./diagnostics.js";
6
+ import { configSources, subpathDependencies } from "./imports.js";
7
+ import { watchFiles, writeChanged } from "./update.js";
8
+ import { basename, resolve } from "node:path";
9
+ import { mkdir } from "node:fs/promises";
10
+ import { fileURLToPath, pathToFileURL } from "node:url";
11
+ import { getProvider } from "files-sdk/providers";
9
12
  //#region src/integration/nitro.ts
10
13
  const AWS_CORE_DEPENDENCIES = [
11
14
  "@aws-sdk/client-s3",
@@ -29,48 +32,38 @@ const optionalAwsSdkDependencies = (adapters, options) => {
29
32
  if (s3Adapters.length === 0) return [];
30
33
  return (s3Adapters.every((adapter) => FETCH_CAPABLE_S3_ADAPTERS.has(adapter)) ? [...AWS_CORE_DEPENDENCIES, AWS_MULTIPART_DEPENDENCY] : [AWS_MULTIPART_DEPENDENCY]).filter((dependency) => !options.resolvable(dependency));
31
34
  };
32
- const nitroMajorVersion = (nitro) => nitro.meta?.majorVersion ?? ("routing" in nitro ? 3 : 2);
33
- const selectedAdapters = (config) => {
34
- const entries = normalizeFilesConfig(config);
35
- const single = entries.has(void 0);
36
- const adapters = [...new Set([...entries.values()].map(({ storage }) => storage.adapter).filter((adapter) => typeof adapter === "string"))].toSorted();
37
- for (const adapter of adapters) if (!getProvider(adapter)) throw new Error(`[nuxt-files-sdk:unknown-adapter] Unknown adapter "${adapter}".`);
38
- return {
39
- adapters,
40
- single
41
- };
42
- };
43
- const gatewayRoutes = (config) => {
44
- const routes = config && typeof config === "object" && "routes" in config ? config.routes : void 0;
45
- if (routes === void 0) return [];
46
- if (!Array.isArray(routes)) throw new Error("[nuxt-files-sdk:invalid-route] routes must be an array.");
47
- const storages = normalizeFilesConfig(config);
48
- const paths = /* @__PURE__ */ new Set();
49
- const selected = [];
50
- for (const route of routes) {
51
- if (!route || typeof route !== "object" || !("path" in route) || typeof route.path !== "string" || !/^\/(?:[\w.~-]+(?:\/[\w.~-]+)*)?$/u.test(route.path)) throw new Error("[nuxt-files-sdk:invalid-route] Each route needs a static absolute path.");
52
- if (paths.has(route.path)) throw new Error(`[nuxt-files-sdk:duplicate-route] Duplicate route "${route.path}".`);
53
- paths.add(route.path);
54
- const name = "storage" in route ? route.storage : void 0;
55
- if (storages.has(void 0) ? name !== void 0 : typeof name !== "string" || !storages.has(name)) throw new Error(`[nuxt-files-sdk:unknown-storage] Invalid storage for route "${route.path}".`);
56
- selected.push(typeof name === "string" ? {
57
- path: route.path,
58
- storage: name
59
- } : { path: route.path });
35
+ const missingAwsSdkShim = (dependency) => `throw new Error(${JSON.stringify(`[nuxt-files-sdk:missing-optional-dependency] ${dependency} is required for this Files SDK operation. Install it or select the provider's fetch client.`)})\n`;
36
+ const awsVirtualId = (dependency) => `virtual:nuxt-files-sdk/optional/${dependency.replaceAll(/[^a-z0-9]+/giu, "-")}`;
37
+ /** Apply the retained Nitro 2 workerd compatibility after native preset resolution. */
38
+ const wireNuxtNitroAwsOptions = (value, options) => {
39
+ const nitro = value;
40
+ const aliases = nitro.options.alias ??= {};
41
+ for (const dependency of [...AWS_CORE_DEPENDENCIES, AWS_MULTIPART_DEPENDENCY]) {
42
+ const id = awsVirtualId(dependency);
43
+ if (aliases[dependency] === id && nitro.options.virtual?.[id] === missingAwsSdkShim(dependency)) {
44
+ delete aliases[dependency];
45
+ delete nitro.options.virtual[id];
46
+ }
47
+ }
48
+ const missing = optionalAwsSdkDependencies(options.adapters, {
49
+ nitroMajor: options.nitroMajor,
50
+ preset: nitro.options.preset,
51
+ resolvable: (dependency) => Object.hasOwn(aliases, dependency) || resolvePackage(dependency, pathToFileURL(options.sdk.manifestPath)).status === "resolved"
52
+ });
53
+ for (const dependency of missing) {
54
+ const id = awsVirtualId(dependency);
55
+ aliases[dependency] = id;
56
+ (nitro.options.virtual ??= {})[id] = missingAwsSdkShim(dependency);
60
57
  }
61
- return selected;
58
+ return missing;
62
59
  };
63
- const factoryName = (adapter) => adapter === "cloudinary" ? "cloudinaryAdapter" : adapter.replaceAll(/-([a-z0-9])/gu, (_, character) => character.toUpperCase());
64
- const providerCode = (adapters) => ({
65
- imports: adapters.map((adapter, index) => `import { ${factoryName(adapter)} as provider${index} } from ${JSON.stringify(`files-sdk/${adapter}`)}`).join("\n"),
66
- factories: adapters.map((adapter, index) => `${JSON.stringify(adapter)}: provider${index}`).join(", ")
67
- });
60
+ const nitroMajorVersion = (nitro) => nitro.meta?.majorVersion ?? ("routing" in nitro ? 3 : 2);
68
61
  const hookTypes = (moduleName) => `
69
62
  declare module ${JSON.stringify(moduleName)} {
70
63
  interface NitroRuntimeHooks {
71
- 'files:action': (payload: { event: import('files-sdk').FilesActionEvent; storage?: string }) => void | Promise<void>
72
- 'files:error': (payload: { event: import('files-sdk').FilesErrorEvent; storage?: string }) => void | Promise<void>
73
- 'files:retry': (payload: { event: import('files-sdk').FilesRetryEvent; storage?: string }) => void | Promise<void>
64
+ 'files:action': (payload: { event: import('#files-sdk').FilesActionEvent; storage?: string }) => void | Promise<void>
65
+ 'files:error': (payload: { event: import('#files-sdk').FilesErrorEvent; storage?: string }) => void | Promise<void>
66
+ 'files:retry': (payload: { event: import('#files-sdk').FilesRetryEvent; storage?: string }) => void | Promise<void>
74
67
  }
75
68
  }
76
69
  `;
@@ -81,25 +74,77 @@ declare module 'nuxt-files-sdk/runtime' {
81
74
  interface NuxtFilesSingleStorage { value: SingleStorage<typeof config> }
82
75
  interface NuxtFilesStorageRegistry extends StorageRegistry<typeof config> {}
83
76
  }
84
- ${hookTypes(nitroMajor >= 3 ? "nitro/types" : "nitropack/types")}
77
+ ${nitroMajor === 0 ? "" : hookTypes(nitroMajor >= 3 ? "nitro/types" : "nitropack/types")}
85
78
  export {}
86
79
  `;
87
80
  const setupNitroFilesIntegration = async (nitro, options) => {
81
+ const sdk = resolveOwnedSdk();
82
+ for (const dependency of [...AWS_CORE_DEPENDENCIES, AWS_MULTIPART_DEPENDENCY]) {
83
+ const shim = resolve(nitro.options.rootDir, nitro.options.buildDir, "nuxt-files-sdk", `${dependency.replaceAll(/[^a-z0-9]+/giu, "-")}.mjs`).replaceAll("\\", "/");
84
+ if (nitro.options.alias?.[dependency]?.replaceAll("\\", "/") === shim) delete nitro.options.alias[dependency];
85
+ }
86
+ nitro.options.alias = registerSdkAliases(nitro.options.alias ?? {}, sdk);
88
87
  const configPath = options.configPath.replaceAll("\\", "/");
89
- const config = await loadFilesConfig({
90
- configPath,
91
- environments: options.environments,
92
- alias: nitro.options.alias,
93
- injectImports: nitro.unimport?.injectImports.bind(nitro.unimport)
94
- });
95
- if (!config) return false;
96
- const selected = selectedAdapters(config);
88
+ const graph = configSources(configPath, nitro.options.alias);
89
+ const inputHashes = Object.fromEntries(graph.files.map((path) => [path, fileHash(path)]));
90
+ let prepared;
91
+ try {
92
+ prepared = await prepareFilesConfig({
93
+ configPath,
94
+ environments: options.environments,
95
+ alias: nitro.options.alias,
96
+ injectImports: nitro.unimport?.injectImports.bind(nitro.unimport)
97
+ });
98
+ } catch (error) {
99
+ try {
100
+ const requirements = graph.sdkImports.flatMap((subpath) => subpathDependencies(sdk, subpath));
101
+ reportDependencyIssues(diagnoseDependencies(sdk, requirements, nitro.options.alias), dependencySession(nitro.options.rootDir), (message) => nitro.logger?.warn(message));
102
+ } catch {}
103
+ throw error;
104
+ }
105
+ if (Object.entries(inputHashes).some(([path, hash]) => fileHash(path) !== hash)) throw new Error("[nuxt-files-sdk:config-changed] Files configuration changed during preparation. Retry preparation.");
106
+ let watcher;
107
+ if (nitro.options.dev) {
108
+ const roots = /* @__PURE__ */ new Set([
109
+ nitro.options.rootDir,
110
+ nitro.options.workspaceDir ?? nitro.options.rootDir,
111
+ ...graph.files.map((path) => resolve(path, ".."))
112
+ ]);
113
+ const watched = [
114
+ ...graph.files,
115
+ sdk.manifestPath,
116
+ ...[...roots].flatMap((root) => [
117
+ "package.json",
118
+ "bun.lock",
119
+ "package-lock.json",
120
+ "pnpm-lock.yaml",
121
+ "yarn.lock",
122
+ "node_modules/.package-lock.json"
123
+ ].map((name) => resolve(root, name)))
124
+ ];
125
+ watcher = watchFiles(watched, async () => {
126
+ watcher?.add(configSources(configPath, nitro.options.alias).files);
127
+ await prepareFilesConfig({
128
+ configPath,
129
+ environments: options.environments,
130
+ alias: nitro.options.alias,
131
+ injectImports: nitro.unimport?.injectImports.bind(nitro.unimport)
132
+ });
133
+ await (options.restart ? options.restart() : nitro.hooks.callHook?.("restart"));
134
+ }, (error) => nitro.logger?.warn(error instanceof Error ? error.message : String(error)));
135
+ nitro.hooks.hook("close", () => watcher?.close());
136
+ }
137
+ if (!prepared) return false;
97
138
  const directory = resolve(nitro.options.rootDir, nitro.options.buildDir, "nuxt-files-sdk");
98
139
  const typesPath = resolve(directory, "storage-registry.d.ts");
99
140
  let writeRuntime;
100
141
  const writeTypes = async () => {
101
142
  await mkdir(directory, { recursive: true });
102
- await writeFile(typesPath, storageTypes(configPath, nitroMajorVersion(nitro)));
143
+ await writeChanged(typesPath, storageTypes(configPath, nitroMajorVersion(nitro)));
144
+ await writeChanged(resolve(directory, "tsconfig.json"), JSON.stringify({
145
+ compilerOptions: { paths: sdkTypePaths(sdk) },
146
+ include: [typesPath.replaceAll("\\", "/")]
147
+ }, null, 2));
103
148
  };
104
149
  await writeTypes();
105
150
  nitro.hooks.hook("types:extend", async (types) => {
@@ -108,28 +153,29 @@ const setupNitroFilesIntegration = async (nitro, options) => {
108
153
  const tsConfig = types.tsConfig ??= {};
109
154
  (tsConfig.include ??= []).push(typesPath);
110
155
  const paths = (tsConfig.compilerOptions ??= {}).paths ??= {};
111
- paths["files-sdk"] ??= [resolve(nitro.options.rootDir, "node_modules/files-sdk").replaceAll("\\", "/")];
156
+ Object.assign(paths, sdkTypePaths(sdk));
112
157
  });
113
- const routes = gatewayRoutes(config);
114
- const { adapters } = selected;
115
- const providers = providerCode(adapters);
158
+ const { routes, adapters, providers, environment } = prepared;
116
159
  const internalPath = fileURLToPath(new URL("../runtime/internal.js", import.meta.url)).replaceAll("\\", "/");
117
- const environment = Object.fromEntries(adapters.map((adapter) => [adapter, listEnvVars(adapter).filter((variable) => variable.readBy === "files-sdk").map((variable) => [variable.key, ...variable.aliases ?? []])]));
118
- const require = createRequire(resolve(nitro.options.rootDir, "package.json"));
119
160
  const aliases = nitro.options.alias ?? {};
120
- const shimFiles = optionalAwsSdkDependencies(adapters, {
161
+ const awsShims = optionalAwsSdkDependencies(adapters, {
121
162
  nitroMajor: nitroMajorVersion(nitro),
122
163
  preset: nitro.options.preset,
123
164
  resolvable: (dependency) => {
124
165
  if (Object.hasOwn(aliases, dependency)) return true;
125
- try {
126
- require.resolve(dependency);
127
- return true;
128
- } catch {
129
- return false;
130
- }
166
+ return resolvePackage(dependency, pathToFileURL(sdk.manifestPath)).status === "resolved";
131
167
  }
132
- }).map((dependency) => ({
168
+ });
169
+ const adapterDependencies = new Map(prepared.adapters.map((adapter) => [adapter, subpathDependencies(sdk, `files-sdk/${adapter}`)]));
170
+ const requirements = [...prepared.entries.values()].flatMap((entry) => storageDependencies(entry, deploymentTarget(nitro.options.preset), typeof entry.storage.adapter === "string" ? adapterDependencies.get(entry.storage.adapter) : []));
171
+ for (const subpath of graph.sdkImports) requirements.push(...subpathDependencies(sdk, subpath));
172
+ const dependencyDiagnostics = diagnoseDependencies(sdk, requirements, aliases, awsShims);
173
+ watcher?.add(requirements.flatMap(({ dependency, conditions }) => {
174
+ const result = resolvePackage(dependency, pathToFileURL(sdk.manifestPath), conditions);
175
+ return result.status === "missing" ? [] : [result.package.manifestPath];
176
+ }));
177
+ reportDependencyIssues(dependencyDiagnostics, dependencySession(nitro.options.rootDir), (message) => nitro.logger?.warn(message));
178
+ const shimFiles = awsShims.map((dependency) => ({
133
179
  dependency,
134
180
  path: resolve(directory, `${dependency.replaceAll(/[^a-z0-9]+/giu, "-")}.mjs`)
135
181
  }));
@@ -139,7 +185,7 @@ const setupNitroFilesIntegration = async (nitro, options) => {
139
185
  }
140
186
  nitro.unimport?.getInternalContext().addons.push({
141
187
  name: "nuxt-files-sdk-jsdoc",
142
- declaration: (declarations) => declarations.replace(/^([ \t]*)const (useServerFiles|syncFiles|transferFiles): typeof .*\.\2$/gmu, (_, indent, name) => `${name === "useServerFiles" ? `${indent}/** Return the project's Files client, including its configured plugin extensions. */\n` : ""}${indent}const ${name}: typeof import('nuxt-files-sdk/runtime').${name}`)
188
+ declaration: (declarations) => declarations.replace(/^([ \t]*)const (defineFilesConfig|useServerFiles|syncFiles|transferFiles): typeof .*\.\2$/gmu, (_, indent, name) => `${name === "useServerFiles" ? `${indent}/** Return the project's Files client, including its configured plugin extensions. */\n` : ""}${indent}const ${name}: typeof import('nuxt-files-sdk/${name === "defineFilesConfig" ? "config" : "runtime"}').${name}`)
143
189
  });
144
190
  const externals = nitro.options.externals ??= {};
145
191
  (externals.inline ??= []).push("nuxt-files-sdk");
@@ -156,18 +202,17 @@ const setupNitroFilesIntegration = async (nitro, options) => {
156
202
  nitro.options.plugins.push(pluginPath.replaceAll("\\", "/"));
157
203
  writeRuntime = async () => {
158
204
  await mkdir(directory, { recursive: true });
159
- await Promise.all(shimFiles.map(({ dependency, path }) => writeFile(path, `throw new Error(${JSON.stringify(`[nuxt-files-sdk:missing-optional-dependency] ${dependency} is required for this Files SDK operation. Install it or select the provider's fetch client.`)})\n`)));
160
- const originalSource = await readFile(configPath, "utf8");
161
- const importedSource = nitro.unimport ? (await nitro.unimport.injectImports(originalSource, configPath)).code : originalSource;
162
- await writeFile(selectedPath, pruneFilesConfigSource(importedSource, configPath, options.environments));
205
+ await Promise.all(shimFiles.map(({ dependency, path }) => writeChanged(path, missingAwsSdkShim(dependency))));
206
+ await writeChanged(selectedPath, prepared.source);
163
207
  const mergePath = fileURLToPath(new URL("../config/merge.js", import.meta.url)).replaceAll("\\", "/");
164
208
  const selectedBranches = options.environments.flatMap((name) => [`raw[${JSON.stringify(`$${name}`)}]`, `raw.$env?.[${JSON.stringify(name)}]`]).toReversed();
165
- await writeFile(resolvedPath, `import raw from ${JSON.stringify(selectedPath.replaceAll("\\", "/"))}\nimport { mergeFilesConfig } from ${JSON.stringify(mergePath)}\nconst resolved = mergeFilesConfig(${[...selectedBranches, "raw"].join(", ")})\nexport default { storage: resolved.storage, routes: resolved.routes }\n`);
166
- await writeFile(pluginPath, `import config from ${JSON.stringify(resolvedPath.replaceAll("\\", "/"))}
209
+ await writeChanged(resolvedPath, `import raw from ${JSON.stringify(selectedPath.replaceAll("\\", "/"))}\nimport { mergeFilesConfig } from ${JSON.stringify(mergePath)}\nconst resolved = mergeFilesConfig(${[...selectedBranches, "raw"].join(", ")})\nexport default { storage: resolved.storage, routes: resolved.routes }\n`);
210
+ await writeChanged(pluginPath, `import config from ${JSON.stringify(resolvedPath.replaceAll("\\", "/"))}
167
211
  ${providers.imports}
168
212
  import { configureFiles } from ${JSON.stringify(internalPath)}
169
213
 
170
214
  export default (nitroApp) => configureFiles(config, {
215
+ ${nitro.options.dev ? `dependencies: ${JSON.stringify(dependencyDiagnostics)},` : ""}
171
216
  factories: { ${providers.factories} },
172
217
  environment: ${JSON.stringify(environment)},
173
218
  hooks: {
@@ -179,20 +224,18 @@ export default (nitroApp) => configureFiles(config, {
179
224
  `);
180
225
  await Promise.all(routePaths.map((path, index) => {
181
226
  const route = routes[index];
182
- const handler = nitroMajorVersion(nitro) >= 3 ? "router.handle(event.req)" : "createRouteHandler(router)(event)";
183
- return writeFile(path, `import config from ${JSON.stringify(resolvedPath.replaceAll("\\", "/"))}
184
- import { createFilesRouter } from 'files-sdk/api'
185
- ${nitroMajorVersion(nitro) >= 3 ? "" : "import { createRouteHandler } from 'files-sdk/nitro'"}
227
+ const request = nitroMajorVersion(nitro) >= 3 ? "event.req" : "toWebRequest(event)";
228
+ return writeChanged(path, `import config from ${JSON.stringify(resolvedPath.replaceAll("\\", "/"))}
229
+ import { createFilesRouter } from '#files-sdk/api'
230
+ ${nitroMajorVersion(nitro) >= 3 ? "" : "import { toWebRequest } from 'h3'"}
231
+ import { nitroRequestEvent, nitroResponse } from ${JSON.stringify(fileURLToPath(new URL("./nitro-event.js", import.meta.url)).replaceAll("\\", "/"))}
186
232
  import { getFiles } from ${JSON.stringify(internalPath)}
233
+ import { resolveGatewaySecret } from ${JSON.stringify(fileURLToPath(new URL("../runtime/gateway-secret.js", import.meta.url)).replaceAll("\\", "/"))}
234
+ ${nitro.options.dev ? `import { developmentConfigCurrent } from ${JSON.stringify(fileURLToPath(new URL("../runtime/development.js", import.meta.url)).replaceAll("\\", "/"))}\nconst inputHashes = ${JSON.stringify(inputHashes)}` : ""}
187
235
 
188
236
  const route = config.routes[${index}]
189
237
  const environmentSecret = typeof process === 'undefined' ? undefined : process.env?.FILES_API_SECRET
190
- const secret = route.authorize
191
- ? route.secret || environmentSecret || crypto.randomUUID() + crypto.randomUUID()
192
- : route.secret
193
- if (route.authorize && !route.secret && !environmentSecret) {
194
- console.warn('[nuxt-files-sdk:gateway-secret] Set FILES_API_SECRET for upload tokens shared across processes.')
195
- }
238
+ const secret = await resolveGatewaySecret(route.secret, environmentSecret)
196
239
  let sharedRouter
197
240
  const makeRouter = (event) => createFilesRouter({
198
241
  ...route,
@@ -200,9 +243,11 @@ const makeRouter = (event) => createFilesRouter({
200
243
  secret,
201
244
  authorize: route.authorize && ((context) => route.authorize({ ...context, event })),
202
245
  })
203
- export default (event) => {
204
- const router = route.authorize ? makeRouter(event) : (sharedRouter ??= makeRouter(event))
205
- return ${handler}
246
+ export default async (event) => {
247
+ ${nitro.options.dev ? `if (!developmentConfigCurrent(inputHashes)) return new Response('Files configuration is being updated.', { status: 503 })` : ""}
248
+ const portable = nitroRequestEvent(${request}, event.context)
249
+ const router = route.authorize ? makeRouter(portable) : (sharedRouter ??= makeRouter(portable))
250
+ return nitroResponse(await router.handle(portable.req), portable)
206
251
  }
207
252
  `);
208
253
  }));
@@ -210,7 +255,41 @@ export default (event) => {
210
255
  await writeRuntime();
211
256
  return true;
212
257
  };
258
+ /** Preserve TypeScript compilation when Nuxt places its build directory inside node_modules. */
259
+ const selectedSourcePlugin = (options) => {
260
+ const selected = options.selected.replaceAll("\\", "/");
261
+ const id = `nuxt-files-sdk:${basename(selected)}`;
262
+ return {
263
+ name: "nuxt-files-sdk-selected-source",
264
+ resolveId(source, importer) {
265
+ if (source.replaceAll("\\", "/") === selected) return id;
266
+ if (importer === id) return this.resolve(source, options.configPath, { skipSelf: true });
267
+ return null;
268
+ },
269
+ load(source) {
270
+ return source === id ? options.source : null;
271
+ }
272
+ };
273
+ };
274
+ /** Preserve Nuxt's runtime inline rules when Nitro 2 receives native Windows resolver paths. */
275
+ const inlineNuxtRuntime = (id) => id.includes("\\") && /^(?:nuxt|nuxt3|nuxt-nightly)\/dist\//u.test(id.replaceAll("\\", "/").split("node_modules/").at(-1));
276
+ /** Native bundle settings stay in the Nitro adapter, including Nuxt's Nitro-backed path. */
277
+ const wireNuxtNitroOptions = (value, options) => {
278
+ const config = value;
279
+ config.alias = registerSdkAliases({
280
+ ...config.alias,
281
+ "nuxt-files-sdk/runtime": options.runtime,
282
+ "#nuxt-files-sdk/registry": options.registry
283
+ }, options.sdk);
284
+ const inline = (config.externals ??= {}).inline ??= [];
285
+ inline.push("nuxt-files-sdk", options.runtime, options.registry, options.resolved, options.selected);
286
+ inline.push(inlineNuxtRuntime);
287
+ if (!options.development) inline.push("files-sdk");
288
+ ((config.rollupConfig ??= {}).plugins ??= []).push(selectedSourcePlugin({
289
+ selected: options.selected,
290
+ source: options.selectedSource,
291
+ configPath: options.configPath
292
+ }));
293
+ };
213
294
  //#endregion
214
- export { gatewayRoutes, optionalAwsSdkDependencies, providerCode, selectedAdapters, setupNitroFilesIntegration, storageTypes };
215
-
216
- //# sourceMappingURL=nitro.js.map
295
+ export { inlineNuxtRuntime, optionalAwsSdkDependencies, selectedSourcePlugin, setupNitroFilesIntegration, storageTypes, wireNuxtNitroAwsOptions, wireNuxtNitroOptions };
@@ -0,0 +1,23 @@
1
+ import { reportDependencyIssues } from "./diagnostics.js";
2
+ import { getAddDependencyCommand } from "@nuxt/kit";
3
+ //#region src/integration/nuxt-diagnostics.ts
4
+ /** Advice only: Nuxt's installer cannot guarantee zero prompts/installs on every host. */
5
+ const reportNuxtDependencyIssues = async (diagnostics, previous, logger, root) => {
6
+ let message;
7
+ reportDependencyIssues(diagnostics, previous, (value) => {
8
+ message = value;
9
+ });
10
+ if (!message) return;
11
+ const packages = [...new Set(diagnostics.filter((entry) => entry.necessity === "required" && (entry.status === "missing" || entry.status === "incompatible")).map((entry) => {
12
+ const name = entry.dependency.split("/").slice(0, entry.dependency.startsWith("@") ? 2 : 1).join("/");
13
+ return entry.range ? `${name}@${entry.range}` : name;
14
+ }))];
15
+ if (!packages.length) {
16
+ logger.warn(message);
17
+ return;
18
+ }
19
+ const command = await getAddDependencyCommand(packages, root, { dev: false }).catch(() => void 0);
20
+ logger.warn(`${message}\n${command ? `Run: ${command}` : `Add production dependencies: ${packages.join(", ")}`}`);
21
+ };
22
+ //#endregion
23
+ export { reportNuxtDependencyIssues };
@@ -0,0 +1,235 @@
1
+ import { filesBuilderCapabilities, requireFilesGateway } from "./capabilities.js";
2
+ import { fileHash } from "../runtime/development.js";
3
+ import { registerSdkAliases, resolveOwnedSdk, resolvePackage, sdkTypePaths } from "./resolve.js";
4
+ import { prepareFilesConfig } from "../config/prepare.js";
5
+ import { deploymentTarget, storageDependencies } from "./dependencies.js";
6
+ import { dependencySession, diagnoseDependencies } from "./diagnostics.js";
7
+ import { configSources, subpathDependencies } from "./imports.js";
8
+ import { watchFiles } from "./update.js";
9
+ import { optionalAwsSdkDependencies, storageTypes, wireNuxtNitroAwsOptions, wireNuxtNitroOptions } from "./nitro.js";
10
+ import { stopNitroDevReloadOnClose } from "./nitro-dev-close.js";
11
+ import { reportNuxtDependencyIssues } from "./nuxt-diagnostics.js";
12
+ import { withFilesTask } from "./terminal.js";
13
+ import { dirname, resolve } from "node:path";
14
+ import { addServerHandler, addTemplate, addTypeTemplate, resolveServerVariant, useLogger, useTerminal } from "@nuxt/kit";
15
+ import { fileURLToPath, pathToFileURL } from "node:url";
16
+ import { parseSync } from "oxc-parser";
17
+ //#region src/integration/nuxt.ts
18
+ const normalized = (path) => path.replaceAll("\\", "/");
19
+ const runtimeFile = (path) => normalized(fileURLToPath(new URL(path, import.meta.url)));
20
+ /** Supply the module's configuration helper without evaluating any runtime resolver. */
21
+ const injectConfigImports = async (source, filename) => {
22
+ return { code: parseSync(filename ?? "files.config.ts", source, { lang: "ts" }).program.body.some((node) => node.type === "ImportDeclaration" ? node.specifiers.some((specifier) => specifier.local.name === "defineFilesConfig") : node.type === "FunctionDeclaration" ? node.id?.name === "defineFilesConfig" : node.type === "VariableDeclaration" && node.declarations.some((declaration) => declaration.id.type === "Identifier" && declaration.id.name === "defineFilesConfig")) ? source : `import { defineFilesConfig } from ${JSON.stringify(runtimeFile("../config.js"))}\n${source}` };
23
+ };
24
+ /** Generate one runtime import graph; Nuxt never initializes Files through a Nitro startup plugin. */
25
+ const setupNuxtFilesIntegration = async (nuxt, options) => {
26
+ const logger = useLogger("nuxt-files-sdk");
27
+ const terminal = useTerminal();
28
+ const configPath = normalized(options.configPath);
29
+ const sdk = resolveOwnedSdk();
30
+ nuxt.options.alias = registerSdkAliases(nuxt.options.alias, sdk, ["browser", "import"]);
31
+ const preparationAliases = () => {
32
+ const native = nuxt.options.nitro;
33
+ const aliases = "alias" in native && native.alias && typeof native.alias === "object" ? native.alias : {};
34
+ return registerSdkAliases({
35
+ ...nuxt.options.alias,
36
+ ...Object.fromEntries(Object.entries(aliases).filter((entry) => typeof entry[1] === "string"))
37
+ }, sdk, ["node", "import"]);
38
+ };
39
+ const graph = configSources(configPath, preparationAliases());
40
+ const inputHashes = Object.fromEntries(graph.files.map((path) => [path, fileHash(path)]));
41
+ const environments = nuxt.options.nitro.static ? ["production", "prerender"] : [nuxt.options.envName || (nuxt.options.dev ? "development" : "production")];
42
+ const prepare = () => prepareFilesConfig({
43
+ configPath,
44
+ environments,
45
+ alias: preparationAliases(),
46
+ injectImports: injectConfigImports
47
+ });
48
+ let prepared;
49
+ try {
50
+ prepared = await withFilesTask(terminal, "Preparing Files configuration", prepare);
51
+ } catch (error) {
52
+ try {
53
+ await reportNuxtDependencyIssues(diagnoseDependencies(sdk, graph.sdkImports.flatMap((subpath) => subpathDependencies(sdk, subpath)), preparationAliases()), dependencySession(nuxt.options.rootDir), logger, nuxt.options.rootDir);
54
+ } catch {}
55
+ throw error;
56
+ }
57
+ if (Object.entries(inputHashes).some(([path, hash]) => fileHash(path) !== hash)) throw new Error("[nuxt-files-sdk:config-changed] Files configuration changed during preparation. Retry preparation.");
58
+ let watcher;
59
+ if (nuxt.options.dev) {
60
+ const roots = /* @__PURE__ */ new Set([
61
+ nuxt.options.rootDir,
62
+ nuxt.options.workspaceDir,
63
+ ...graph.files.map(dirname)
64
+ ]);
65
+ watcher = watchFiles([
66
+ ...graph.files,
67
+ sdk.manifestPath,
68
+ ...[...roots].flatMap((root) => [
69
+ "package.json",
70
+ "bun.lock",
71
+ "package-lock.json",
72
+ "pnpm-lock.yaml",
73
+ "yarn.lock",
74
+ "node_modules/.package-lock.json"
75
+ ].map((name) => resolve(root, name)))
76
+ ], async () => {
77
+ watcher?.add(configSources(configPath, preparationAliases()).files);
78
+ await withFilesTask(terminal, "Updating Files configuration", prepare);
79
+ await nuxt.callHook("restart");
80
+ }, (error) => logger.warn(error instanceof Error ? error.message : String(error)));
81
+ nuxt.hook("close", () => watcher?.close());
82
+ }
83
+ if (!prepared) return false;
84
+ const major = resolveServerVariant({
85
+ nitro2: 2,
86
+ nitro3: 3,
87
+ nuxt: 0
88
+ }) ?? 0;
89
+ if (nuxt.options.dev && major === 2) nuxt.hook("nitro:init", stopNitroDevReloadOnClose);
90
+ let awsShims = [];
91
+ if (major === 2 && optionalAwsSdkDependencies(prepared.adapters, {
92
+ nitroMajor: major,
93
+ preset: "cloudflare-module",
94
+ resolvable: () => false
95
+ }).length) nuxt.hook("nitro:init", (nitro) => {
96
+ awsShims = wireNuxtNitroAwsOptions(nitro, {
97
+ sdk,
98
+ adapters: prepared.adapters,
99
+ nitroMajor: major
100
+ });
101
+ });
102
+ const capabilities = filesBuilderCapabilities(major === 2 ? "nitro2" : major === 3 ? "nitro3" : "nuxt");
103
+ requireFilesGateway(prepared.routes.length > 0, capabilities.gateway);
104
+ const mode = nuxt.options.dev ? ".dev" : "";
105
+ const template = (name, contents) => normalized(addTemplate({
106
+ filename: `nuxt-files-sdk/${name}${mode}.mjs`,
107
+ write: true,
108
+ getContents: () => contents
109
+ }).dst);
110
+ const selected = normalized(addTemplate({
111
+ filename: `nuxt-files-sdk/selected${mode}.ts`,
112
+ write: true,
113
+ getContents: () => prepared.source
114
+ }).dst);
115
+ const branches = environments.flatMap((name) => [`raw[${JSON.stringify(`$${name}`)}]`, `raw.$env?.[${JSON.stringify(name)}]`]).toReversed();
116
+ const resolved = template("resolved", `import raw from ${JSON.stringify(selected)}\nimport { mergeFilesConfig } from ${JSON.stringify(runtimeFile("../config/merge.js"))}\nconst resolved = mergeFilesConfig(${[...branches, "raw"].join(", ")})\nexport default { storage: resolved.storage, routes: resolved.routes }\n`);
117
+ const adapterDependencies = new Map(prepared.adapters.map((adapter) => [adapter, subpathDependencies(sdk, `files-sdk/${adapter}`)]));
118
+ const requirementsFor = (preset) => [...prepared.entries.values()].flatMap((entry) => storageDependencies(entry, deploymentTarget(preset), typeof entry.storage.adapter === "string" ? adapterDependencies.get(entry.storage.adapter) : []));
119
+ const requirements = requirementsFor("preset" in nuxt.options.nitro && typeof nuxt.options.nitro.preset === "string" ? nuxt.options.nitro.preset : void 0);
120
+ for (const subpath of graph.sdkImports) requirements.push(...subpathDependencies(sdk, subpath));
121
+ const diagnostics = diagnoseDependencies(sdk, requirements, preparationAliases());
122
+ const previous = dependencySession(nuxt.options.rootDir);
123
+ if (!major) await reportNuxtDependencyIssues(diagnostics, previous, logger, nuxt.options.rootDir);
124
+ if (major && requirements.length) nuxt.hook("nitro:init", async (value) => {
125
+ const native = value;
126
+ const resolvedRequirements = requirementsFor(native.options.preset);
127
+ for (const subpath of graph.sdkImports) resolvedRequirements.push(...subpathDependencies(sdk, subpath));
128
+ await reportNuxtDependencyIssues(diagnoseDependencies(sdk, resolvedRequirements, native.options.alias, awsShims), previous, logger, nuxt.options.rootDir);
129
+ });
130
+ watcher?.add(requirements.flatMap(({ dependency, conditions }) => {
131
+ const result = resolvePackage(dependency, pathToFileURL(sdk.manifestPath), conditions);
132
+ return result.status === "missing" ? [] : [result.package.manifestPath];
133
+ }));
134
+ const hooksImport = major === 2 ? "import { useNitroApp } from 'nitropack/runtime'" : major === 3 ? "import { useNitroHooks } from 'nitro/app'" : "";
135
+ const callHook = major === 2 ? "useNitroApp().hooks.callHook" : "useNitroHooks().callHook";
136
+ const registry = template("registry", `import config from ${JSON.stringify(resolved)}\n${prepared.providers.imports}\nimport { configureFiles } from ${JSON.stringify(runtimeFile("../runtime/internal.js"))}\n${hooksImport}\n\nexport const registry = configureFiles(config, {\n factories: { ${prepared.providers.factories} },\n environment: ${JSON.stringify(prepared.environment)},\n ${nuxt.options.dev ? `dependencies: ${JSON.stringify(diagnostics)},` : ""}\n ${major ? `hooks: {\n onAction: (event, storage) => ${callHook}('files:action', { event, storage }),\n onError: (event, storage) => ${callHook}('files:error', { event, storage }),\n onRetry: (event, storage) => ${callHook}('files:retry', { event, storage }),\n },` : ""}\n})\n`);
137
+ const runtime = template("runtime", `import { registry } from '#nuxt-files-sdk/registry'
138
+ import { sync, transfer } from '#files-sdk'
139
+ export const useServerFiles = name => name === undefined ? registry.get() : registry.get(name)
140
+ const files = value => typeof value === 'string' ? registry.get(value) : value
141
+ export const syncFiles = (source, destination, options) => sync(files(source), files(destination), options)
142
+ export const transferFiles = (source, destination, options) => transfer(files(source), files(destination), options)
143
+ `);
144
+ addTypeTemplate({
145
+ filename: `nuxt-files-sdk/runtime${mode}.d.ts`,
146
+ getContents: () => `export * from ${JSON.stringify(runtimeFile("../runtime.js"))}\n`
147
+ }, { nuxt: false });
148
+ nuxt.options.alias["nuxt-files-sdk/runtime"] = runtime;
149
+ nuxt.options.alias["#nuxt-files-sdk/registry"] = registry;
150
+ nuxt.hook("nitro:config", (config) => wireNuxtNitroOptions(config, {
151
+ sdk,
152
+ runtime,
153
+ registry,
154
+ resolved,
155
+ selected,
156
+ selectedSource: prepared.source,
157
+ configPath,
158
+ development: nuxt.options.dev
159
+ }));
160
+ const declarations = addTypeTemplate({
161
+ filename: "nuxt-files-sdk/storage-registry.d.ts",
162
+ getContents: () => storageTypes(configPath, major) + `
163
+ declare global {
164
+ /** Return the project's Files client, including its configured plugin extensions. */
165
+ const useServerFiles: typeof import('nuxt-files-sdk/runtime').useServerFiles
166
+ /** Delegate a mirror operation to the native Files SDK. */
167
+ const syncFiles: typeof import('nuxt-files-sdk/runtime').syncFiles
168
+ /** Delegate a transfer operation to the native Files SDK. */
169
+ const transferFiles: typeof import('nuxt-files-sdk/runtime').transferFiles
170
+ }
171
+ `
172
+ }, {
173
+ nuxt: true,
174
+ nitro: true,
175
+ shared: true
176
+ });
177
+ nuxt.hook("prepare:types", ({ references, nodeReferences, sharedReferences, serverReferences, tsConfig, nodeTsConfig, sharedTsConfig, serverTsConfig }) => {
178
+ for (const values of [
179
+ references,
180
+ nodeReferences,
181
+ sharedReferences,
182
+ serverReferences
183
+ ]) if (!values.some((entry) => "path" in entry && entry.path === declarations.dst)) values.push({ path: declarations.dst });
184
+ for (const config of [
185
+ tsConfig,
186
+ nodeTsConfig,
187
+ sharedTsConfig,
188
+ serverTsConfig
189
+ ]) {
190
+ (config.include ??= []).push(configPath);
191
+ Object.assign((config.compilerOptions ??= {}).paths ??= {}, sdkTypePaths(sdk), { "nuxt-files-sdk/runtime": [runtimeFile("../runtime.d.ts")] });
192
+ }
193
+ });
194
+ for (const [index, route] of prepared.routes.entries()) {
195
+ const handler = template(`gateway-${index}`, `import { defineEventHandler, deriveSecret } from 'nuxt/server'
196
+ import config from ${JSON.stringify(resolved)}
197
+ import { createFilesRouter } from '#files-sdk/api'
198
+ import { useServerFiles } from 'nuxt-files-sdk/runtime'
199
+ import { resolveGatewaySecret } from ${JSON.stringify(runtimeFile("../runtime/gateway-secret.js"))}
200
+ ${nuxt.options.dev ? `import { developmentConfigCurrent } from ${JSON.stringify(runtimeFile("../runtime/development.js"))}\nconst inputHashes = ${JSON.stringify(inputHashes)}` : ""}
201
+ const route = config.routes[${index}]
202
+ let secretPromise
203
+ const gatewaySecret = () => secretPromise ??= resolveGatewaySecret(
204
+ route.secret,
205
+ typeof process === 'undefined' ? undefined : process.env?.FILES_API_SECRET,
206
+ () => deriveSecret(${JSON.stringify(`nuxt-files-sdk:gateway:${route.path}`)}),
207
+ ).catch(error => {
208
+ secretPromise = undefined
209
+ throw error
210
+ })
211
+ let sharedRouter
212
+ const makeRouter = (event, secret) => createFilesRouter({
213
+ ...route,
214
+ files: () => useServerFiles(${route.storage === void 0 ? "" : JSON.stringify(route.storage)}),
215
+ secret,
216
+ authorize: route.authorize && ((context) => route.authorize({ ...context, event })),
217
+ })
218
+ export default defineEventHandler(async event => {
219
+ ${nuxt.options.dev ? "if (!developmentConfigCurrent(inputHashes)) return new Response('Files configuration is being updated.', { status: 503 })" : ""}
220
+ const secret = await gatewaySecret()
221
+ const router = route.authorize
222
+ ? makeRouter(event, secret)
223
+ : (sharedRouter ??= makeRouter(event, secret))
224
+ return router.handle(event.req)
225
+ })
226
+ `);
227
+ addServerHandler({
228
+ route: route.path,
229
+ handler: { nuxt: handler }
230
+ });
231
+ }
232
+ return true;
233
+ };
234
+ //#endregion
235
+ export { setupNuxtFilesIntegration };
@@ -0,0 +1 @@
1
+ import { getPackageInfoSync } from "local-pkg";