@djangocfg/ext-base 1.0.3 → 1.0.5

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 (41) hide show
  1. package/README.md +134 -8
  2. package/dist/api.cjs +40 -0
  3. package/dist/api.d.cts +35 -0
  4. package/dist/api.d.ts +35 -0
  5. package/dist/api.js +2 -0
  6. package/dist/auth.cjs +10 -0
  7. package/dist/auth.d.cts +1 -0
  8. package/dist/auth.d.ts +1 -0
  9. package/dist/auth.js +2 -0
  10. package/dist/chunk-2CHOCDMY.js +237 -0
  11. package/dist/chunk-3RG5ZIWI.js +8 -0
  12. package/dist/chunk-NXFE5CDE.js +140 -0
  13. package/dist/cli.mjs +530 -0
  14. package/dist/hooks.cjs +437 -0
  15. package/dist/hooks.d.cts +97 -0
  16. package/dist/hooks.d.ts +97 -0
  17. package/dist/hooks.js +95 -0
  18. package/dist/index.cjs +345 -0
  19. package/dist/index.d.cts +363 -0
  20. package/dist/index.d.ts +363 -0
  21. package/dist/index.js +3 -0
  22. package/package.json +6 -4
  23. package/src/cli/index.ts +281 -15
  24. package/src/context/ExtensionProvider.tsx +67 -4
  25. package/src/extensionConfig.ts +77 -28
  26. package/templates/extension-template/README.md.template +30 -0
  27. package/templates/extension-template/package.json.template +2 -1
  28. package/templates/extension-template/playground/.gitignore.template +34 -0
  29. package/templates/extension-template/playground/CLAUDE.md +35 -0
  30. package/templates/extension-template/playground/README.md.template +76 -0
  31. package/templates/extension-template/playground/app/globals.css.template +19 -0
  32. package/templates/extension-template/playground/app/layout.tsx.template +32 -0
  33. package/templates/extension-template/playground/app/page.tsx.template +44 -0
  34. package/templates/extension-template/playground/next.config.ts.template +62 -0
  35. package/templates/extension-template/playground/package.json.template +36 -0
  36. package/templates/extension-template/playground/postcss.config.js.template +5 -0
  37. package/templates/extension-template/playground/tsconfig.json.template +18 -0
  38. package/templates/extension-template/src/contexts/__PROVIDER_NAME__Context.tsx +1 -1
  39. package/templates/extension-template/src/contexts/__PROVIDER_NAME__ExtensionProvider.tsx +1 -0
  40. package/templates/extension-template/src/index.ts +12 -4
  41. package/templates/extension-template/src/utils/withSmartProvider.tsx +70 -0
package/dist/hooks.js ADDED
@@ -0,0 +1,95 @@
1
+ import { createExtensionLogger } from './chunk-2CHOCDMY.js';
2
+ export { EXTENSION_CATEGORIES, createExtensionConfig, createExtensionError, createExtensionLogger, extensionConfig, formatErrorMessage, handleExtensionError, isExtensionError } from './chunk-2CHOCDMY.js';
3
+ import { isDevelopment } from './chunk-NXFE5CDE.js';
4
+ export { createExtensionAPI, getApiUrl, getSharedAuthStorage, isDevelopment, isProduction, isStaticBuild } from './chunk-NXFE5CDE.js';
5
+ import './chunk-3RG5ZIWI.js';
6
+ import React, { createContext, useEffect, useContext } from 'react';
7
+ import { jsx, Fragment } from 'react/jsx-runtime';
8
+ import { SWRConfig } from 'swr';
9
+ import { AuthProvider, useAuth } from '@djangocfg/api/auth';
10
+ import { consola } from 'consola';
11
+
12
+ function createExtensionContext({
13
+ displayName,
14
+ errorMessage
15
+ }) {
16
+ const Context = createContext(void 0);
17
+ Context.displayName = displayName;
18
+ const Provider = ({ value, children }) => {
19
+ return /* @__PURE__ */ jsx(Context.Provider, { value, children });
20
+ };
21
+ const useContextHook = () => {
22
+ const context = useContext(Context);
23
+ if (context === void 0) {
24
+ throw new Error(
25
+ errorMessage || `use${displayName} must be used within ${displayName}Provider`
26
+ );
27
+ }
28
+ return context;
29
+ };
30
+ return {
31
+ Context,
32
+ Provider,
33
+ useContext: useContextHook
34
+ };
35
+ }
36
+ var DEFAULT_OPTIONS = {
37
+ revalidateOnFocus: false,
38
+ revalidateOnReconnect: false,
39
+ revalidateIfStale: false
40
+ };
41
+ var registeredExtensions = /* @__PURE__ */ new Set();
42
+ var authWarningShown = false;
43
+ function AuthChecker({ children }) {
44
+ useAuth();
45
+ return /* @__PURE__ */ jsx(Fragment, { children });
46
+ }
47
+ var AuthErrorBoundary = class extends React.Component {
48
+ state = { hasError: false, errorMessage: "" };
49
+ static getDerivedStateFromError(error) {
50
+ if (error?.message?.includes("useAuth must be used within an AuthProvider")) {
51
+ return { hasError: true, errorMessage: error.message };
52
+ }
53
+ return null;
54
+ }
55
+ componentDidCatch(error) {
56
+ if (this.state.hasError && !authWarningShown) {
57
+ authWarningShown = true;
58
+ consola.error(
59
+ "\u274C AuthProvider not found in parent component!\n Extension components require AuthProvider wrapper.\n Auto-wrapping applied as a safety measure.\n\n Please wrap your page with <BaseApp> or <AuthProvider>:\n\n import { BaseApp } from '@djangocfg/layouts';\n \n <BaseApp>\n <YourPageWithExtensions />\n </BaseApp>"
60
+ );
61
+ }
62
+ }
63
+ render() {
64
+ if (this.state.hasError) {
65
+ return /* @__PURE__ */ jsx(AuthProvider, { children: this.props.children });
66
+ }
67
+ return this.props.children;
68
+ }
69
+ };
70
+ function ExtensionProvider({ children, metadata, options = {} }) {
71
+ const config = { ...DEFAULT_OPTIONS, ...options };
72
+ useEffect(() => {
73
+ if (registeredExtensions.has(metadata.name)) {
74
+ if (isDevelopment) {
75
+ console.warn(
76
+ `[ExtensionProvider] Extension "${metadata.name}" is already registered. This might indicate that the extension is mounted multiple times.`
77
+ );
78
+ }
79
+ return;
80
+ }
81
+ registeredExtensions.add(metadata.name);
82
+ if (isDevelopment) {
83
+ const logger = createExtensionLogger({ tag: "ext-base", level: "info" });
84
+ logger.info(
85
+ `Extension registered: ${metadata.displayName || metadata.name} v${metadata.version}`
86
+ );
87
+ }
88
+ return () => {
89
+ registeredExtensions.delete(metadata.name);
90
+ };
91
+ }, [metadata.name, metadata.version, metadata.displayName]);
92
+ return /* @__PURE__ */ jsx(AuthErrorBoundary, { children: /* @__PURE__ */ jsx(AuthChecker, { children: /* @__PURE__ */ jsx(SWRConfig, { value: config, children }) }) });
93
+ }
94
+
95
+ export { ExtensionProvider, createExtensionContext };
package/dist/index.cjs ADDED
@@ -0,0 +1,345 @@
1
+ 'use strict';
2
+
3
+ var consola = require('consola');
4
+
5
+ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
6
+ get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
7
+ }) : x)(function(x) {
8
+ if (typeof require !== "undefined") return require.apply(this, arguments);
9
+ throw Error('Dynamic require of "' + x + '" is not supported');
10
+ });
11
+
12
+ // src/types/context.ts
13
+ var EXTENSION_CATEGORIES = [
14
+ { title: "Forms", value: "forms" },
15
+ { title: "Payments", value: "payments" },
16
+ { title: "Content", value: "content" },
17
+ { title: "Support", value: "support" },
18
+ { title: "Utilities", value: "utilities" },
19
+ { title: "Analytics", value: "analytics" },
20
+ { title: "Security", value: "security" },
21
+ { title: "Integration", value: "integration" },
22
+ { title: "Other", value: "other" }
23
+ ];
24
+
25
+ // src/utils/createExtensionConfig.ts
26
+ function replaceWorkspaceDeps(deps) {
27
+ if (!deps) return void 0;
28
+ const result = {};
29
+ for (const [key, value] of Object.entries(deps)) {
30
+ result[key] = value === "workspace:*" ? "latest" : value;
31
+ }
32
+ return result;
33
+ }
34
+ function createExtensionConfig(packageJson, config) {
35
+ const author = typeof packageJson.author === "string" ? packageJson.author : packageJson.author?.name || "Unknown";
36
+ const githubUrl = typeof packageJson.repository === "string" ? packageJson.repository : packageJson.repository?.url;
37
+ const packageNameWithoutScope = packageJson.name.split("/").pop() || packageJson.name;
38
+ const downloadUrl = `https://registry.npmjs.org/${packageJson.name}/-/${packageNameWithoutScope}-${packageJson.version}.tgz`;
39
+ const marketplaceId = packageJson.name.replace("@", "").replace("/", "-");
40
+ return {
41
+ // From package.json
42
+ name: config.name,
43
+ version: packageJson.version,
44
+ author,
45
+ description: packageJson.description,
46
+ keywords: packageJson.keywords,
47
+ license: packageJson.license,
48
+ homepage: packageJson.homepage,
49
+ githubUrl,
50
+ packageDependencies: replaceWorkspaceDeps(packageJson.dependencies),
51
+ packagePeerDependencies: replaceWorkspaceDeps(packageJson.peerDependencies),
52
+ packageDevDependencies: replaceWorkspaceDeps(packageJson.devDependencies),
53
+ // From manual config
54
+ displayName: config.displayName,
55
+ category: config.category,
56
+ features: config.features,
57
+ examples: config.examples,
58
+ minVersion: config.minVersion,
59
+ githubStars: config.githubStars,
60
+ // Auto-generated
61
+ npmUrl: `https://www.npmjs.com/package/${packageJson.name}`,
62
+ marketplaceId,
63
+ // Only generate marketplace URL for official @djangocfg extensions
64
+ marketplaceUrl: packageJson.name.startsWith("@djangocfg/ext-") ? `https://hub.djangocfg.com/extensions/${marketplaceId}` : void 0,
65
+ installCommand: `pnpm add ${packageJson.name}`,
66
+ downloadUrl,
67
+ tags: packageJson.keywords,
68
+ preview: `https://unpkg.com/${packageJson.name}@latest/preview.png`
69
+ };
70
+ }
71
+
72
+ // package.json
73
+ var package_default = {
74
+ name: "@djangocfg/ext-base",
75
+ version: "1.0.5",
76
+ description: "Base utilities and common code for DjangoCFG extensions",
77
+ keywords: [
78
+ "django",
79
+ "djangocfg",
80
+ "extension",
81
+ "base",
82
+ "utilities",
83
+ "typescript",
84
+ "react"
85
+ ],
86
+ author: {
87
+ name: "DjangoCFG",
88
+ url: "https://djangocfg.com"
89
+ },
90
+ homepage: "https://hub.djangocfg.com/extensions/djangocfg-ext-base",
91
+ repository: {
92
+ type: "git",
93
+ url: "https://github.com/markolofsen/django-cfg.git",
94
+ directory: "extensions/base"
95
+ },
96
+ license: "MIT",
97
+ peerDependencies: {
98
+ "@djangocfg/api": "workspace:*",
99
+ consola: "^3.4.2",
100
+ react: "^18 || ^19",
101
+ "react-dom": "^18 || ^19",
102
+ swr: "^2.3.7"
103
+ },
104
+ dependencies: {
105
+ chalk: "^5.3.0",
106
+ consola: "^3.4.2",
107
+ picocolors: "^1.1.1",
108
+ prompts: "^2.4.2"
109
+ },
110
+ devDependencies: {
111
+ "@djangocfg/api": "workspace:*",
112
+ "@djangocfg/typescript-config": "workspace:*",
113
+ "@types/node": "^24.7.2",
114
+ "@types/prompts": "^2.4.9",
115
+ "@types/react": "^19.0.0",
116
+ swr: "^2.3.7",
117
+ tsup: "^8.5.0",
118
+ tsx: "^4.19.2",
119
+ typescript: "^5.9.3"
120
+ }
121
+ };
122
+
123
+ // src/extensionConfig.ts
124
+ var extensionConfig = createExtensionConfig(package_default, {
125
+ name: "base",
126
+ displayName: "Extension Base",
127
+ category: "utilities",
128
+ features: [
129
+ "CLI for creating extensions",
130
+ "Next.js 15 playground for rapid development",
131
+ "Smart Provider Pattern for flexible component usage",
132
+ "Extension context and provider system",
133
+ "Pagination hooks (standard & infinite scroll)",
134
+ "API client utilities with auth integration",
135
+ "Type-safe context creation helpers",
136
+ "Logging system with structured output",
137
+ "Environment detection (dev, prod, static)",
138
+ "TypeScript types and utilities"
139
+ ],
140
+ minVersion: "2.0.0",
141
+ examples: [
142
+ {
143
+ title: "Create Extension with CLI",
144
+ description: "Interactive wizard with playground environment",
145
+ code: `# Create extension with interactive wizard
146
+ pnpm dlx @djangocfg/ext-base create
147
+
148
+ # Quick test extension (auto-cleanup)
149
+ pnpm dlx @djangocfg/ext-base test
150
+
151
+ # List all extensions
152
+ pnpm dlx @djangocfg/ext-base list`,
153
+ language: "bash"
154
+ },
155
+ {
156
+ title: "Development with Playground",
157
+ description: "Start Next.js playground for rapid testing",
158
+ code: `cd your-extension
159
+
160
+ # Start playground (auto-builds extension + opens browser)
161
+ pnpm dev:playground
162
+
163
+ # Build for production
164
+ pnpm build
165
+
166
+ # Type check
167
+ pnpm check`,
168
+ language: "bash"
169
+ },
170
+ {
171
+ title: "Smart Provider Pattern",
172
+ description: "Components that work standalone or with shared context",
173
+ code: `import { withSmartProvider } from '@your-org/my-extension';
174
+
175
+ // Your component
176
+ function MyComponent() {
177
+ const { data } = useMyExtension();
178
+ return <div>{data}</div>;
179
+ }
180
+
181
+ // Wrap with smart provider
182
+ export const MySmartComponent = withSmartProvider(MyComponent);
183
+
184
+ // Usage 1: Standalone (auto-wrapped)
185
+ <MySmartComponent />
186
+
187
+ // Usage 2: Manual provider (shared context)
188
+ <MyExtensionProvider>
189
+ <MySmartComponent />
190
+ <MySmartComponent />
191
+ </MyExtensionProvider>`,
192
+ language: "tsx"
193
+ },
194
+ {
195
+ title: "Extension Provider",
196
+ description: "Register and wrap your extension",
197
+ code: `import { ExtensionProvider } from '@djangocfg/ext-base/hooks';
198
+ import { extensionConfig } from './config';
199
+
200
+ export function MyExtensionProvider({ children }) {
201
+ return (
202
+ <ExtensionProvider metadata={extensionConfig}>
203
+ {children}
204
+ </ExtensionProvider>
205
+ );
206
+ }`,
207
+ language: "tsx"
208
+ },
209
+ {
210
+ title: "Pagination Hooks",
211
+ description: "Built-in hooks for standard and infinite scroll",
212
+ code: `import { usePagination, useInfinitePagination } from '@djangocfg/ext-base/hooks';
213
+
214
+ // Standard pagination
215
+ const { items, page, totalPages, nextPage, prevPage } = usePagination({
216
+ keyPrefix: 'articles',
217
+ fetcher: async (page, pageSize) => api.articles.list({ page, page_size: pageSize }),
218
+ pageSize: 20,
219
+ });
220
+
221
+ // Infinite scroll
222
+ const { items, isLoading, hasMore, loadMore } = useInfinitePagination({
223
+ keyPrefix: 'articles',
224
+ fetcher: async (page, pageSize) => api.articles.list({ page, page_size: pageSize }),
225
+ pageSize: 20,
226
+ });`,
227
+ language: "tsx"
228
+ }
229
+ ]
230
+ });
231
+
232
+ // src/metadata.ts
233
+ ({
234
+ version: package_default.version,
235
+ npmUrl: `https://www.npmjs.com/package/${package_default.name}`,
236
+ installCommand: `pnpm add ${package_default.name}`,
237
+ packagePeerDependencies: package_default.peerDependencies});
238
+
239
+ // src/config.ts
240
+ var isDevelopment = process.env.NODE_ENV === "development";
241
+ var isProduction = process.env.NODE_ENV === "production";
242
+ var isStaticBuild = process.env.STATIC_BUILD === "true";
243
+ function getApiUrl() {
244
+ return process.env.NEXT_PUBLIC_API_URL || "/api";
245
+ }
246
+
247
+ // src/api/createExtensionAPI.ts
248
+ function createExtensionAPI(APIClass) {
249
+ let storage;
250
+ try {
251
+ const { api: accountsApi } = __require("@djangocfg/api");
252
+ storage = accountsApi._storage;
253
+ } catch (error) {
254
+ storage = void 0;
255
+ }
256
+ const apiUrl = isStaticBuild ? "" : getApiUrl();
257
+ return new APIClass(apiUrl, storage ? { storage } : void 0);
258
+ }
259
+ function getSharedAuthStorage() {
260
+ try {
261
+ const { api: accountsApi } = __require("@djangocfg/api");
262
+ return accountsApi._storage;
263
+ } catch (error) {
264
+ return void 0;
265
+ }
266
+ }
267
+
268
+ // src/utils/errors.ts
269
+ function isExtensionError(error) {
270
+ return typeof error === "object" && error !== null && "message" in error && "timestamp" in error;
271
+ }
272
+ function createExtensionError(error, code, details) {
273
+ const message = error instanceof Error ? error.message : String(error);
274
+ return {
275
+ message,
276
+ code,
277
+ details,
278
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
279
+ };
280
+ }
281
+ function formatErrorMessage(error) {
282
+ if (isExtensionError(error)) {
283
+ return error.code ? `[${error.code}] ${error.message}` : error.message;
284
+ }
285
+ if (error instanceof Error) {
286
+ return error.message;
287
+ }
288
+ return String(error);
289
+ }
290
+ function handleExtensionError(error, logger, callback) {
291
+ const extensionError = isExtensionError(error) ? error : createExtensionError(error);
292
+ if (logger) {
293
+ logger.error("Extension error:", extensionError);
294
+ }
295
+ if (callback) {
296
+ callback(extensionError);
297
+ }
298
+ }
299
+ var isDevelopment2 = process.env.NODE_ENV === "development";
300
+ var LEVEL_MAP = {
301
+ debug: 4,
302
+ info: 3,
303
+ warn: 2,
304
+ error: 1
305
+ };
306
+ function createExtensionLogger(options) {
307
+ const { tag, level = "info", enabled = true } = options;
308
+ if (!enabled) {
309
+ const noop = () => {
310
+ };
311
+ return {
312
+ info: noop,
313
+ warn: noop,
314
+ error: noop,
315
+ debug: noop,
316
+ success: noop
317
+ };
318
+ }
319
+ const logLevel = isDevelopment2 ? LEVEL_MAP[level] : LEVEL_MAP.error;
320
+ const consola$1 = consola.createConsola({
321
+ level: logLevel
322
+ }).withTag(tag);
323
+ return {
324
+ info: (...args) => consola$1.info(...args),
325
+ warn: (...args) => consola$1.warn(...args),
326
+ error: (...args) => consola$1.error(...args),
327
+ debug: (...args) => consola$1.debug(...args),
328
+ success: (...args) => consola$1.success(...args)
329
+ };
330
+ }
331
+
332
+ exports.EXTENSION_CATEGORIES = EXTENSION_CATEGORIES;
333
+ exports.createExtensionAPI = createExtensionAPI;
334
+ exports.createExtensionConfig = createExtensionConfig;
335
+ exports.createExtensionError = createExtensionError;
336
+ exports.createExtensionLogger = createExtensionLogger;
337
+ exports.extensionConfig = extensionConfig;
338
+ exports.formatErrorMessage = formatErrorMessage;
339
+ exports.getApiUrl = getApiUrl;
340
+ exports.getSharedAuthStorage = getSharedAuthStorage;
341
+ exports.handleExtensionError = handleExtensionError;
342
+ exports.isDevelopment = isDevelopment;
343
+ exports.isExtensionError = isExtensionError;
344
+ exports.isProduction = isProduction;
345
+ exports.isStaticBuild = isStaticBuild;