@notur/sdk 1.4.5 → 1.4.7

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 (51) hide show
  1. package/README.md +136 -1
  2. package/bin/notur-create.js +586 -0
  3. package/bin/notur-doctor.js +123 -0
  4. package/bin/notur-pack.js +13 -7
  5. package/bin/notur-push.js +331 -0
  6. package/bin/notur-sync.js +199 -0
  7. package/bin/notur-validate.js +200 -0
  8. package/bin/notur.js +46 -0
  9. package/dist/events.d.ts +10 -0
  10. package/dist/events.d.ts.map +1 -1
  11. package/dist/events.js +7 -0
  12. package/dist/events.js.map +1 -1
  13. package/dist/hooks/useExtensionConfig.d.ts +23 -4
  14. package/dist/hooks/useExtensionConfig.d.ts.map +1 -1
  15. package/dist/hooks/useExtensionConfig.js +8 -1
  16. package/dist/hooks/useExtensionConfig.js.map +1 -1
  17. package/dist/hooks/useNavigate.d.ts +27 -6
  18. package/dist/hooks/useNavigate.d.ts.map +1 -1
  19. package/dist/hooks/useNavigate.js +11 -2
  20. package/dist/hooks/useNavigate.js.map +1 -1
  21. package/dist/hooks/useNoturEvent.d.ts +12 -0
  22. package/dist/hooks/useNoturEvent.d.ts.map +1 -1
  23. package/dist/hooks/useNoturEvent.js +12 -0
  24. package/dist/hooks/useNoturEvent.js.map +1 -1
  25. package/dist/hooks/usePermission.d.ts +10 -1
  26. package/dist/hooks/usePermission.d.ts.map +1 -1
  27. package/dist/hooks/usePermission.js +10 -1
  28. package/dist/hooks/usePermission.js.map +1 -1
  29. package/dist/hooks/useServerContext.d.ts +18 -3
  30. package/dist/hooks/useServerContext.d.ts.map +1 -1
  31. package/dist/hooks/useServerContext.js +8 -1
  32. package/dist/hooks/useServerContext.js.map +1 -1
  33. package/dist/hooks/useUserContext.d.ts +10 -2
  34. package/dist/hooks/useUserContext.d.ts.map +1 -1
  35. package/dist/hooks/useUserContext.js +2 -0
  36. package/dist/hooks/useUserContext.js.map +1 -1
  37. package/dist/index.d.ts +6 -1
  38. package/dist/index.d.ts.map +1 -1
  39. package/dist/index.js.map +1 -1
  40. package/dist/types.d.ts +179 -20
  41. package/dist/types.d.ts.map +1 -1
  42. package/dist/types.js +6 -1
  43. package/dist/types.js.map +1 -1
  44. package/examples/red-button/.env.example +2 -0
  45. package/examples/red-button/README.md +30 -0
  46. package/examples/red-button/extension.yaml +9 -0
  47. package/examples/red-button/package.json +29 -0
  48. package/examples/red-button/resources/frontend/src/index.tsx +32 -0
  49. package/examples/red-button/tsconfig.json +13 -0
  50. package/examples/red-button/webpack.config.js +17 -0
  51. package/package.json +11 -3
package/bin/notur-pack.js CHANGED
@@ -257,13 +257,19 @@ async function pack(sourceDir, outputPath, options = {}) {
257
257
  ];
258
258
 
259
259
  if (options.dryRun) {
260
- console.log('\nDry run mode (no archive written).');
261
- console.log(`Would create: ${outputFullPath}`);
262
- console.log(`Would include: ${archiveEntries.length} files`);
263
- if (deterministicArgs.length > 0) {
264
- console.log('Deterministic archive mode: enabled (GNU tar flags).');
265
- } else {
266
- console.log('Deterministic archive mode: not available (GNU tar not detected).');
260
+ try {
261
+ console.log('\nDry run mode (no archive written).');
262
+ console.log(`Would create: ${outputFullPath}`);
263
+ console.log(`Would include: ${archiveEntries.length} files`);
264
+ if (deterministicArgs.length > 0) {
265
+ console.log('Deterministic archive mode: enabled (GNU tar flags).');
266
+ } else {
267
+ console.log('Deterministic archive mode: not available (GNU tar not detected).');
268
+ }
269
+ } finally {
270
+ if (!checksumsExisted && fs.existsSync(checksumsPath)) {
271
+ fs.unlinkSync(checksumsPath);
272
+ }
267
273
  }
268
274
  return;
269
275
  }
@@ -0,0 +1,331 @@
1
+ #!/usr/bin/env node
2
+
3
+ const fs = require('fs');
4
+ const os = require('os');
5
+ const path = require('path');
6
+ const { spawnSync } = require('child_process');
7
+
8
+ function parseArgs() {
9
+ const args = process.argv.slice(2);
10
+ const options = {
11
+ path: '.',
12
+ archive: null,
13
+ host: null,
14
+ key: null,
15
+ envFile: null,
16
+ endpoint: '/api/notur/dev/push',
17
+ force: true,
18
+ noBuild: false,
19
+ keepArchive: false,
20
+ };
21
+
22
+ for (let i = 0; i < args.length; i++) {
23
+ const arg = args[i];
24
+ if (arg === '--archive') {
25
+ options.archive = args[++i];
26
+ } else if (arg === '--host') {
27
+ options.host = args[++i];
28
+ } else if (arg === '--key') {
29
+ options.key = args[++i];
30
+ } else if (arg === '--env-file') {
31
+ options.envFile = args[++i];
32
+ } else if (arg === '--endpoint') {
33
+ options.endpoint = args[++i];
34
+ } else if (arg === '--no-force') {
35
+ options.force = false;
36
+ } else if (arg === '--no-build') {
37
+ options.noBuild = true;
38
+ } else if (arg === '--keep-archive') {
39
+ options.keepArchive = true;
40
+ } else if (arg === '--help' || arg === '-h') {
41
+ usage(0);
42
+ } else if (!arg.startsWith('-')) {
43
+ options.path = arg;
44
+ } else {
45
+ console.error(`Unknown argument: ${arg}`);
46
+ usage(1);
47
+ }
48
+ }
49
+
50
+ return options;
51
+ }
52
+
53
+ function usage(code) {
54
+ console.log(`Usage:
55
+ npx notur-push [path] --host https://panel.example.com --key notur_xxx
56
+ npx @notur/sdk push [path] --host https://panel.example.com --key notur_xxx
57
+
58
+ Options:
59
+ --archive <file> Upload an existing .notur archive instead of packing
60
+ --host <url> Remote Pterodactyl panel URL
61
+ --key <token> Notur remote push token
62
+ --env-file <file> Load values from a custom env file
63
+ --endpoint <path> Remote push endpoint (default: /api/notur/dev/push)
64
+ --no-build Skip npm/yarn/pnpm/bun build before packing
65
+ --no-force Do not overwrite an already installed extension
66
+ --keep-archive Keep the temporary archive generated for this push`);
67
+ process.exit(code);
68
+ }
69
+
70
+ function loadManifest(dir) {
71
+ const manifestPath = path.join(dir, 'extension.yaml');
72
+ if (!fs.existsSync(manifestPath)) {
73
+ return { id: path.basename(dir), version: 'dev' };
74
+ }
75
+
76
+ const raw = fs.readFileSync(manifestPath, 'utf8');
77
+ const id = raw.match(/^id:\s*["']?([^"'\n]+)["']?/m)?.[1]?.trim();
78
+ const version = raw.match(/^version:\s*["']?([^"'\n]+)["']?/m)?.[1]?.trim();
79
+
80
+ return {
81
+ id: id || path.basename(dir),
82
+ version: version || 'dev',
83
+ };
84
+ }
85
+
86
+ function detectPackageManager(dir) {
87
+ if (fs.existsSync(path.join(dir, 'bun.lockb')) || fs.existsSync(path.join(dir, 'bun.lock'))) return 'bun';
88
+ if (fs.existsSync(path.join(dir, 'pnpm-lock.yaml'))) return 'pnpm';
89
+ if (fs.existsSync(path.join(dir, 'yarn.lock'))) return 'yarn';
90
+ if (fs.existsSync(path.join(dir, 'package-lock.json')) || fs.existsSync(path.join(dir, 'package.json'))) return 'npm';
91
+ return null;
92
+ }
93
+
94
+ function buildCommand(packageManager) {
95
+ switch (packageManager) {
96
+ case 'bun':
97
+ return ['bun', ['run', 'build']];
98
+ case 'pnpm':
99
+ return ['pnpm', ['run', 'build']];
100
+ case 'yarn':
101
+ return ['yarn', ['run', 'build']];
102
+ case 'npm':
103
+ return ['npm', ['run', 'build']];
104
+ default:
105
+ return null;
106
+ }
107
+ }
108
+
109
+ function maybeBuild(dir, noBuild) {
110
+ if (noBuild || !fs.existsSync(path.join(dir, 'package.json'))) {
111
+ return;
112
+ }
113
+
114
+ const packageJson = JSON.parse(fs.readFileSync(path.join(dir, 'package.json'), 'utf8'));
115
+ if (!packageJson.scripts || !packageJson.scripts.build) {
116
+ return;
117
+ }
118
+
119
+ const packageManager = detectPackageManager(dir);
120
+ const command = buildCommand(packageManager);
121
+ if (!command) {
122
+ console.warn('No supported package manager found; skipping build.');
123
+ return;
124
+ }
125
+
126
+ console.log(`Running ${command[0]} ${command[1].join(' ')}...`);
127
+ const result = spawnSync(command[0], command[1], {
128
+ cwd: dir,
129
+ stdio: 'inherit',
130
+ });
131
+
132
+ if (result.status !== 0) {
133
+ console.error('Build failed; aborting push.');
134
+ process.exit(result.status ?? 1);
135
+ }
136
+ }
137
+
138
+ function packArchive(dir) {
139
+ const manifest = loadManifest(dir);
140
+ const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'notur-push-'));
141
+ const archiveName = `${manifest.id.replace('/', '-')}-${manifest.version}.notur`;
142
+ const archivePath = path.join(tmpDir, archiveName);
143
+ const packScript = path.join(__dirname, 'notur-pack.js');
144
+
145
+ const result = spawnSync(process.execPath, [packScript, dir, '--output', archivePath], {
146
+ stdio: 'inherit',
147
+ });
148
+
149
+ if (result.status !== 0) {
150
+ console.error('Packaging failed; aborting push.');
151
+ process.exit(result.status ?? 1);
152
+ }
153
+
154
+ return { archivePath, tmpDir };
155
+ }
156
+
157
+ function resolveUrl(host, endpoint, force) {
158
+ const base = host.replace(/\/+$/, '');
159
+ const pathPart = endpoint.startsWith('/') ? endpoint : `/${endpoint}`;
160
+ const url = new URL(`${base}${pathPart}`);
161
+ if (!force) {
162
+ url.searchParams.set('force', '0');
163
+ }
164
+ return url;
165
+ }
166
+
167
+ async function pushArchive(options, archivePath) {
168
+ if (typeof fetch !== 'function' || typeof FormData !== 'function' || typeof Blob !== 'function') {
169
+ console.error('Error: notur-push requires Node.js 18+ for fetch/FormData support.');
170
+ process.exit(1);
171
+ }
172
+
173
+ const url = resolveUrl(options.host, options.endpoint, options.force);
174
+ const data = fs.readFileSync(archivePath);
175
+ const form = new FormData();
176
+ form.append('extension', new Blob([data]), path.basename(archivePath));
177
+
178
+ const signaturePath = `${archivePath}.sig`;
179
+ if (fs.existsSync(signaturePath)) {
180
+ const signature = fs.readFileSync(signaturePath);
181
+ form.append('signature', new Blob([signature]), path.basename(signaturePath));
182
+ }
183
+
184
+ console.log(`Uploading ${path.basename(archivePath)} to ${url.origin}...`);
185
+
186
+ const response = await fetch(url, {
187
+ method: 'POST',
188
+ headers: {
189
+ Authorization: `Bearer ${options.key}`,
190
+ Accept: 'application/json',
191
+ },
192
+ body: form,
193
+ });
194
+
195
+ const text = await response.text();
196
+ let payload = null;
197
+ try {
198
+ payload = text ? JSON.parse(text) : null;
199
+ } catch {
200
+ payload = null;
201
+ }
202
+
203
+ if (!response.ok) {
204
+ console.error(`Remote push failed (${response.status}).`);
205
+ if (payload?.message) {
206
+ console.error(payload.message);
207
+ } else if (text) {
208
+ console.error(text);
209
+ }
210
+ process.exit(1);
211
+ }
212
+
213
+ if (payload) {
214
+ console.log(`Pushed ${payload.id || 'extension'} v${payload.version || 'unknown'}.`);
215
+ if (payload.output) {
216
+ console.log(payload.output.trim());
217
+ }
218
+ } else {
219
+ console.log('Push completed.');
220
+ }
221
+ }
222
+
223
+ function parseEnvValue(value) {
224
+ let parsed = value.trim();
225
+ if (
226
+ (parsed.startsWith('"') && parsed.endsWith('"')) ||
227
+ (parsed.startsWith("'") && parsed.endsWith("'"))
228
+ ) {
229
+ parsed = parsed.slice(1, -1);
230
+ }
231
+ return parsed;
232
+ }
233
+
234
+ function loadEnvFile(filePath) {
235
+ if (!fs.existsSync(filePath)) {
236
+ return {};
237
+ }
238
+
239
+ const values = {};
240
+ const lines = fs.readFileSync(filePath, 'utf8').split(/\r?\n/);
241
+
242
+ for (const line of lines) {
243
+ const trimmed = line.trim();
244
+ if (!trimmed || trimmed.startsWith('#')) {
245
+ continue;
246
+ }
247
+
248
+ const match = trimmed.match(/^(?:export\s+)?([A-Za-z_][A-Za-z0-9_]*)=(.*)$/);
249
+ if (!match) {
250
+ continue;
251
+ }
252
+
253
+ values[match[1]] = parseEnvValue(match[2]);
254
+ }
255
+
256
+ return values;
257
+ }
258
+
259
+ function resolvePushConfig(options, extensionPath) {
260
+ const envFile = options.envFile
261
+ ? path.resolve(options.envFile)
262
+ : path.join(extensionPath, '.env');
263
+ const fileEnv = loadEnvFile(envFile);
264
+
265
+ return {
266
+ ...options,
267
+ host: options.host || process.env.NOTUR_HOST || fileEnv.NOTUR_HOST || null,
268
+ key:
269
+ options.key ||
270
+ process.env.NOTUR_API_KEY ||
271
+ process.env.NOTUR_PUSH_KEY ||
272
+ fileEnv.NOTUR_API_KEY ||
273
+ fileEnv.NOTUR_PUSH_KEY ||
274
+ null,
275
+ endpoint: options.endpoint || fileEnv.NOTUR_ENDPOINT || '/api/notur/dev/push',
276
+ };
277
+ }
278
+
279
+ async function main() {
280
+ let options = parseArgs();
281
+ const extensionPath = path.resolve(options.path);
282
+
283
+ if (!fs.existsSync(extensionPath) || !fs.statSync(extensionPath).isDirectory()) {
284
+ console.error(`Error: extension path does not exist: ${extensionPath}`);
285
+ process.exit(1);
286
+ }
287
+
288
+ options = resolvePushConfig(options, extensionPath);
289
+
290
+ if (!options.host) {
291
+ console.error('Error: --host is required, or set NOTUR_HOST in the environment or local .env.');
292
+ process.exit(1);
293
+ }
294
+
295
+ if (!options.key) {
296
+ console.error('Error: --key is required, or set NOTUR_PUSH_KEY / NOTUR_API_KEY in the environment or local .env.');
297
+ process.exit(1);
298
+ }
299
+
300
+ if (!options.archive) {
301
+ maybeBuild(extensionPath, options.noBuild);
302
+ }
303
+
304
+ let archivePath = options.archive ? path.resolve(options.archive) : null;
305
+ let tmpDir = null;
306
+ if (!archivePath) {
307
+ const packed = packArchive(extensionPath);
308
+ archivePath = packed.archivePath;
309
+ tmpDir = packed.tmpDir;
310
+ }
311
+
312
+ if (!fs.existsSync(archivePath)) {
313
+ console.error(`Error: archive does not exist: ${archivePath}`);
314
+ process.exit(1);
315
+ }
316
+
317
+ try {
318
+ await pushArchive(options, archivePath);
319
+ } finally {
320
+ if (tmpDir && !options.keepArchive) {
321
+ fs.rmSync(tmpDir, { recursive: true, force: true });
322
+ } else if (tmpDir) {
323
+ console.log(`Kept archive at ${archivePath}`);
324
+ }
325
+ }
326
+ }
327
+
328
+ main().catch(error => {
329
+ console.error(error?.message || String(error));
330
+ process.exit(1);
331
+ });
@@ -0,0 +1,199 @@
1
+ #!/usr/bin/env node
2
+
3
+ const fs = require('fs');
4
+ const path = require('path');
5
+ const yaml = require('yaml');
6
+
7
+ function parseArgs() {
8
+ const args = process.argv.slice(2);
9
+ const options = {
10
+ path: '.',
11
+ forceWebpack: false,
12
+ };
13
+
14
+ for (let i = 0; i < args.length; i++) {
15
+ const arg = args[i];
16
+ if (arg === '--force-webpack') {
17
+ options.forceWebpack = true;
18
+ } else if (arg === '--help' || arg === '-h') {
19
+ usage(0);
20
+ } else if (!arg.startsWith('-')) {
21
+ options.path = arg;
22
+ } else {
23
+ console.error(`Unknown argument: ${arg}`);
24
+ usage(1);
25
+ }
26
+ }
27
+
28
+ return options;
29
+ }
30
+
31
+ function usage(code) {
32
+ console.log(`Usage:
33
+ npx notur-sync [path]
34
+ npx @notur/sdk sync [path]
35
+
36
+ Options:
37
+ --force-webpack Regenerate webpack.config.js from extension.yaml`);
38
+ process.exit(code);
39
+ }
40
+
41
+ function readManifest(extensionPath) {
42
+ const manifestPath = ['extension.yaml', 'extension.yml']
43
+ .map(file => path.join(extensionPath, file))
44
+ .find(file => fs.existsSync(file));
45
+
46
+ if (!manifestPath) {
47
+ console.error(`Error: extension.yaml not found in ${extensionPath}`);
48
+ process.exit(1);
49
+ }
50
+
51
+ const manifest = yaml.parse(fs.readFileSync(manifestPath, 'utf8'));
52
+ if (!manifest?.id || !manifest?.version) {
53
+ console.error('Error: extension.yaml must contain id and version.');
54
+ process.exit(1);
55
+ }
56
+
57
+ return manifest;
58
+ }
59
+
60
+ function packageName(id) {
61
+ return id.replace('/', '-');
62
+ }
63
+
64
+ function studly(value) {
65
+ return value
66
+ .split('-')
67
+ .filter(Boolean)
68
+ .map(part => part.charAt(0).toUpperCase() + part.slice(1))
69
+ .join('');
70
+ }
71
+
72
+ function libraryName(id) {
73
+ return id
74
+ .split(/[\/-]/)
75
+ .filter(Boolean)
76
+ .map(studly)
77
+ .join('');
78
+ }
79
+
80
+ function bundlePath(manifest) {
81
+ return manifest?.frontend?.bundle || 'resources/frontend/dist/extension.js';
82
+ }
83
+
84
+ function syncPackageJson(extensionPath, manifest) {
85
+ const packagePath = path.join(extensionPath, 'package.json');
86
+ const current = fs.existsSync(packagePath)
87
+ ? JSON.parse(fs.readFileSync(packagePath, 'utf8'))
88
+ : {};
89
+
90
+ const next = {
91
+ ...current,
92
+ name: packageName(manifest.id),
93
+ version: manifest.version,
94
+ private: current.private ?? true,
95
+ scripts: {
96
+ ...(current.scripts || {}),
97
+ build: current.scripts?.build || 'webpack-cli --mode production --config webpack.config.js',
98
+ dev: current.scripts?.dev || 'webpack-cli --mode development --watch --config webpack.config.js',
99
+ pack: current.scripts?.pack || 'notur-pack',
100
+ push: current.scripts?.push || 'notur-push',
101
+ sync: current.scripts?.sync || 'notur-sync',
102
+ validate: current.scripts?.validate || 'notur-validate',
103
+ doctor: current.scripts?.doctor || 'notur-doctor',
104
+ },
105
+ peerDependencies: {
106
+ react: '^16.14.0',
107
+ 'react-dom': '^16.14.0',
108
+ ...(current.peerDependencies || {}),
109
+ },
110
+ devDependencies: {
111
+ '@notur/sdk': '^1.4.7',
112
+ '@types/react': '^16.14.0',
113
+ '@types/react-dom': '^16.9.0',
114
+ react: '^16.14.0',
115
+ 'react-dom': '^16.14.0',
116
+ 'ts-loader': '^9.5.0',
117
+ typescript: '^5.3.0',
118
+ webpack: '^5.90.0',
119
+ 'webpack-cli': '^6.0.0',
120
+ ...(current.devDependencies || {}),
121
+ },
122
+ };
123
+
124
+ fs.writeFileSync(packagePath, JSON.stringify(next, null, 2) + '\n');
125
+ console.log(` synced ${path.relative(process.cwd(), packagePath)}`);
126
+ }
127
+
128
+ function webpackTemplate(manifest) {
129
+ const bundle = bundlePath(manifest);
130
+ const filename = path.basename(bundle);
131
+ const outputDir = path.dirname(bundle);
132
+
133
+ return `const path = require('path');
134
+ const base = require('@notur/sdk/webpack.extension.config');
135
+
136
+ module.exports = {
137
+ ...base,
138
+ entry: './resources/frontend/src/index.tsx',
139
+ output: {
140
+ ...base.output,
141
+ filename: '${filename}',
142
+ path: path.resolve(__dirname, '${outputDir.replace(/\\/g, '/')}'),
143
+ library: {
144
+ ...base.output.library,
145
+ name: '__NOTUR_EXT_${libraryName(manifest.id)}__',
146
+ type: 'umd',
147
+ },
148
+ },
149
+ };
150
+ `;
151
+ }
152
+
153
+ function syncWebpack(extensionPath, manifest, force) {
154
+ if (!manifest.frontend) {
155
+ return;
156
+ }
157
+
158
+ const webpackPath = path.join(extensionPath, 'webpack.config.js');
159
+ if (fs.existsSync(webpackPath) && !force) {
160
+ return;
161
+ }
162
+
163
+ fs.writeFileSync(webpackPath, webpackTemplate(manifest));
164
+ console.log(` synced ${path.relative(process.cwd(), webpackPath)}`);
165
+ }
166
+
167
+ function syncEnvExample(extensionPath) {
168
+ const envExamplePath = path.join(extensionPath, '.env.example');
169
+ if (fs.existsSync(envExamplePath)) {
170
+ return;
171
+ }
172
+
173
+ fs.writeFileSync(envExamplePath, `NOTUR_HOST=https://panel.example.com
174
+ NOTUR_PUSH_KEY=notur_xxx
175
+ `);
176
+ console.log(` synced ${path.relative(process.cwd(), envExamplePath)}`);
177
+ }
178
+
179
+ function main() {
180
+ const options = parseArgs();
181
+ const extensionPath = path.resolve(options.path);
182
+ if (!fs.existsSync(extensionPath) || !fs.statSync(extensionPath).isDirectory()) {
183
+ console.error(`Error: extension path does not exist: ${extensionPath}`);
184
+ process.exit(1);
185
+ }
186
+
187
+ const manifest = readManifest(extensionPath);
188
+ console.log(`Syncing ${manifest.id} from extension.yaml`);
189
+
190
+ if (manifest.frontend) {
191
+ syncPackageJson(extensionPath, manifest);
192
+ syncWebpack(extensionPath, manifest, options.forceWebpack);
193
+ syncEnvExample(extensionPath);
194
+ }
195
+
196
+ console.log('Done.');
197
+ }
198
+
199
+ main();