create-glanceway-source 1.0.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/dist/index.js ADDED
@@ -0,0 +1,161 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/index.ts
4
+ import * as fs from "fs";
5
+ import * as path from "path";
6
+ import * as readline from "readline";
7
+ import { fileURLToPath } from "url";
8
+ var __filename = fileURLToPath(import.meta.url);
9
+ var __dirname = path.dirname(__filename);
10
+ var CATEGORIES = [
11
+ "Developer",
12
+ "News",
13
+ "Social",
14
+ "Finance",
15
+ "Entertainment",
16
+ "Productivity",
17
+ "Other"
18
+ ];
19
+ async function prompt(question, defaultValue) {
20
+ const rl = readline.createInterface({
21
+ input: process.stdin,
22
+ output: process.stdout
23
+ });
24
+ return new Promise((resolve2) => {
25
+ const displayQuestion = defaultValue ? `${question} (${defaultValue}): ` : `${question}: `;
26
+ rl.question(displayQuestion, (answer) => {
27
+ rl.close();
28
+ resolve2(answer.trim() || defaultValue || "");
29
+ });
30
+ });
31
+ }
32
+ async function confirm(question) {
33
+ const answer = await prompt(`${question} (y/N)`);
34
+ return answer.toLowerCase() === "y" || answer.toLowerCase() === "yes";
35
+ }
36
+ async function selectCategory() {
37
+ console.log("\nSelect a category:");
38
+ CATEGORIES.forEach((cat, index2) => {
39
+ console.log(` ${index2 + 1}. ${cat}`);
40
+ });
41
+ const selection = await prompt("Enter number", "1");
42
+ const index = parseInt(selection, 10) - 1;
43
+ if (index >= 0 && index < CATEGORIES.length) {
44
+ return CATEGORIES[index];
45
+ }
46
+ return "Other";
47
+ }
48
+ function toKebabCase(str) {
49
+ return str.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
50
+ }
51
+ async function getConfig(projectName) {
52
+ const name = toKebabCase(projectName);
53
+ const version = await prompt("Version", "1.0.0");
54
+ const displayName = await prompt("Display name", projectName);
55
+ const description = await prompt("Description", "A Glanceway information source");
56
+ const author = await prompt("Author name");
57
+ if (!author) {
58
+ console.error("\u274C Error: Author name is required.");
59
+ process.exit(1);
60
+ }
61
+ const authorUrl = await prompt("Author URL (optional)");
62
+ const category = await selectCategory();
63
+ return {
64
+ name,
65
+ version,
66
+ displayName,
67
+ description,
68
+ author,
69
+ authorUrl,
70
+ category
71
+ };
72
+ }
73
+ function renderTemplate(content, config) {
74
+ const authorBlock = config.authorUrl ? `author: ${config.author}
75
+ author_url: ${config.authorUrl}` : `author: ${config.author}`;
76
+ return content.replace(/<%=\s*name\s*%>/g, config.name).replace(/<%=\s*version\s*%>/g, config.version).replace(/<%=\s*displayName\s*%>/g, config.displayName).replace(/<%=\s*description\s*%>/g, config.description).replace(/<%-\s*authorBlock\s*%>/g, authorBlock).replace(/<%=\s*category\s*%>/g, config.category).replace(/<%=\s*author\s*%>/g, config.author);
77
+ }
78
+ function copyTemplate(templateDir, targetDir, config) {
79
+ const entries = fs.readdirSync(templateDir, { withFileTypes: true });
80
+ for (const entry of entries) {
81
+ const srcPath = path.join(templateDir, entry.name);
82
+ let targetName = entry.name;
83
+ if (targetName.endsWith(".ejs")) {
84
+ targetName = targetName.slice(0, -4);
85
+ }
86
+ const targetPath = path.join(targetDir, targetName);
87
+ if (entry.isDirectory()) {
88
+ fs.mkdirSync(targetPath, { recursive: true });
89
+ copyTemplate(srcPath, targetPath, config);
90
+ } else {
91
+ let content = fs.readFileSync(srcPath, "utf-8");
92
+ content = renderTemplate(content, config);
93
+ fs.writeFileSync(targetPath, content);
94
+ }
95
+ }
96
+ }
97
+ function removeDir(dir) {
98
+ if (fs.existsSync(dir)) {
99
+ fs.rmSync(dir, { recursive: true, force: true });
100
+ }
101
+ }
102
+ async function main() {
103
+ const args = process.argv.slice(2);
104
+ if (args[0] === "--help" || args[0] === "-h") {
105
+ console.log(`
106
+ Usage: create-glanceway-source [project-name]
107
+
108
+ Creates a new Glanceway source project with TypeScript support.
109
+
110
+ Example:
111
+ npm create glanceway-source
112
+ npm create glanceway-source my-source
113
+ `);
114
+ process.exit(0);
115
+ }
116
+ console.log("\n\u{1F4E6} Creating a new Glanceway source\n");
117
+ let projectName = args[0];
118
+ if (!projectName) {
119
+ projectName = await prompt("Project name", "my-source");
120
+ }
121
+ if (!projectName) {
122
+ console.error("\u274C Error: Project name is required.");
123
+ process.exit(1);
124
+ }
125
+ const targetDir = path.resolve(process.cwd(), projectName);
126
+ if (fs.existsSync(targetDir)) {
127
+ const overwrite = await confirm(`Directory "${projectName}" already exists. Overwrite?`);
128
+ if (!overwrite) {
129
+ console.log("Cancelled.");
130
+ process.exit(0);
131
+ }
132
+ removeDir(targetDir);
133
+ }
134
+ const config = await getConfig(projectName);
135
+ console.log("\n\u{1F680} Creating project...\n");
136
+ fs.mkdirSync(targetDir, { recursive: true });
137
+ const templateDir = path.join(__dirname, "..", "template");
138
+ if (!fs.existsSync(templateDir)) {
139
+ console.error("\u274C Error: Template directory not found.");
140
+ process.exit(1);
141
+ }
142
+ copyTemplate(templateDir, targetDir, config);
143
+ console.log(`\u2705 Created project "${projectName}"
144
+ `);
145
+ console.log("Next steps:");
146
+ console.log(` cd ${projectName}`);
147
+ console.log(" npm install");
148
+ console.log(" npm run dev # Watch mode");
149
+ console.log(" npm run build # Build for production");
150
+ console.log("\nOutput files:");
151
+ console.log(" dist/index.js - Compiled source code");
152
+ console.log(" dist/manifest.yaml - Source metadata");
153
+ console.log("\nTo contribute to glanceway-sources:");
154
+ console.log(" 1. Copy dist/ contents to sources/<your-username>/<source-name>/");
155
+ console.log(" 2. Submit a PR to the glanceway-sources repository");
156
+ console.log("");
157
+ }
158
+ main().catch((error) => {
159
+ console.error("Error:", error);
160
+ process.exit(1);
161
+ });
package/package.json ADDED
@@ -0,0 +1,34 @@
1
+ {
2
+ "name": "create-glanceway-source",
3
+ "version": "1.0.0",
4
+ "description": "Create a new Glanceway information source with TypeScript",
5
+ "type": "module",
6
+ "bin": {
7
+ "create-glanceway-source": "./dist/index.js"
8
+ },
9
+ "files": [
10
+ "dist",
11
+ "template"
12
+ ],
13
+ "scripts": {
14
+ "build": "esbuild src/index.ts --bundle --platform=node --format=esm --outfile=dist/index.js --banner:js='#!/usr/bin/env node'",
15
+ "dev": "npm run build && node dist/index.js"
16
+ },
17
+ "keywords": [
18
+ "glanceway",
19
+ "source",
20
+ "create",
21
+ "cli",
22
+ "template"
23
+ ],
24
+ "author": "codytseng",
25
+ "license": "MIT",
26
+ "devDependencies": {
27
+ "@types/node": "^20.10.0",
28
+ "esbuild": "^0.19.0",
29
+ "typescript": "^5.3.3"
30
+ },
31
+ "engines": {
32
+ "node": ">=18"
33
+ }
34
+ }
@@ -0,0 +1,6 @@
1
+ id: <%= author %>/<%= name %>
2
+ version: <%= version %>
3
+ name: <%= displayName %>
4
+ description: <%= description %>
5
+ <%- authorBlock %>
6
+ category: <%= category %>
@@ -0,0 +1,14 @@
1
+ {
2
+ "name": "<%= name %>",
3
+ "version": "1.0.0",
4
+ "description": "<%= description %>",
5
+ "scripts": {
6
+ "build": "esbuild src/index.ts --bundle --platform=node --format=cjs --outfile=dist/index.js && cp manifest.yaml dist/",
7
+ "dev": "esbuild src/index.ts --bundle --platform=node --format=cjs --outfile=dist/index.js --watch"
8
+ },
9
+ "devDependencies": {
10
+ "@types/node": "^20.10.0",
11
+ "esbuild": "^0.19.0",
12
+ "typescript": "^5.3.3"
13
+ }
14
+ }
@@ -0,0 +1,9 @@
1
+ import type { GlancewayAPI, SourceMethods } from './types';
2
+
3
+ module.exports = (api: GlancewayAPI): SourceMethods => {
4
+ return {
5
+ async refresh() {
6
+ // TODO: Implement your source logic here
7
+ },
8
+ };
9
+ };
@@ -0,0 +1,154 @@
1
+ /**
2
+ * Glanceway Source API
3
+ *
4
+ * The main API object passed to your source function.
5
+ */
6
+ export interface GlancewayAPI {
7
+ /** Send information items to Glanceway for display */
8
+ emit(items: InfoItem[]): void;
9
+
10
+ /** Make HTTP requests */
11
+ fetch(url: string, options?: FetchOptions): Promise<FetchResponse>;
12
+
13
+ /** Log messages for debugging */
14
+ log(level: 'info' | 'error' | 'warn' | 'debug', message: string): void;
15
+
16
+ /** Persistent key-value storage */
17
+ storage: StorageAPI;
18
+
19
+ /** Access user-configured values */
20
+ config: ConfigAPI;
21
+
22
+ /** Create WebSocket connections */
23
+ websocket: WebSocketAPI;
24
+ }
25
+
26
+ /**
27
+ * Information item displayed in Glanceway
28
+ */
29
+ export interface InfoItem {
30
+ /** Unique identifier for the item */
31
+ id: string | number;
32
+
33
+ /** Main display text */
34
+ title: string;
35
+
36
+ /** Secondary text below the title */
37
+ subtitle?: string;
38
+
39
+ /** Link opened when item is clicked */
40
+ url?: string;
41
+
42
+ /** Time associated with the item (ISO string, Unix timestamp, or Date) */
43
+ timestamp?: Date | string | number;
44
+ }
45
+
46
+ /**
47
+ * HTTP request options
48
+ */
49
+ export interface FetchOptions {
50
+ /** HTTP method (default: GET) */
51
+ method?: 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH';
52
+
53
+ /** Request headers */
54
+ headers?: Record<string, string>;
55
+
56
+ /** Request body (for POST/PUT) */
57
+ body?: string;
58
+
59
+ /** Request timeout in milliseconds (default: 30000) */
60
+ timeout?: number;
61
+ }
62
+
63
+ /**
64
+ * HTTP response
65
+ */
66
+ export interface FetchResponse {
67
+ /** True if status code is 200-299 */
68
+ ok: boolean;
69
+
70
+ /** HTTP status code */
71
+ status: number;
72
+
73
+ /** Response headers */
74
+ headers: Record<string, string>;
75
+
76
+ /** Raw response body */
77
+ text: string;
78
+
79
+ /** Parsed JSON (if response is valid JSON) */
80
+ json?: unknown;
81
+ }
82
+
83
+ /**
84
+ * Persistent storage API
85
+ *
86
+ * Data persists between refreshes and app restarts.
87
+ */
88
+ export interface StorageAPI {
89
+ /** Get a stored value */
90
+ get(key: string): unknown;
91
+
92
+ /** Store a value */
93
+ set(key: string, value: unknown): void;
94
+ }
95
+
96
+ /**
97
+ * Configuration API
98
+ *
99
+ * Access user-configured values defined in manifest.yaml.
100
+ */
101
+ export interface ConfigAPI {
102
+ /** Get a config value by key */
103
+ get(key: string): string | undefined;
104
+
105
+ /** Get all config values */
106
+ getAll(): Record<string, string>;
107
+ }
108
+
109
+ /**
110
+ * WebSocket API for real-time connections
111
+ */
112
+ export interface WebSocketAPI {
113
+ /** Connect to a WebSocket server */
114
+ connect(url: string, callbacks: WebSocketCallbacks): Promise<WebSocketConnection>;
115
+ }
116
+
117
+ /**
118
+ * WebSocket event callbacks
119
+ */
120
+ export interface WebSocketCallbacks {
121
+ /** Called when connection is established */
122
+ onConnect?: (ws: WebSocketConnection) => void;
123
+
124
+ /** Called when a message is received */
125
+ onMessage?: (data: string) => void;
126
+
127
+ /** Called when an error occurs */
128
+ onError?: (error: string) => void;
129
+
130
+ /** Called when connection is closed */
131
+ onClose?: (code: number) => void;
132
+ }
133
+
134
+ /**
135
+ * WebSocket connection
136
+ */
137
+ export interface WebSocketConnection {
138
+ /** Send a message */
139
+ send(message: string): Promise<void>;
140
+
141
+ /** Close the connection */
142
+ close(): void;
143
+ }
144
+
145
+ /**
146
+ * Source methods returned by your source function
147
+ */
148
+ export interface SourceMethods {
149
+ /** Called on startup and periodically based on user settings */
150
+ refresh?: () => Promise<void> | void;
151
+
152
+ /** Called when source is stopped (for cleanup) */
153
+ stop?: () => Promise<void> | void;
154
+ }
@@ -0,0 +1,16 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "module": "ESNext",
5
+ "moduleResolution": "bundler",
6
+ "strict": true,
7
+ "esModuleInterop": true,
8
+ "skipLibCheck": true,
9
+ "forceConsistentCasingInFileNames": true,
10
+ "outDir": "dist",
11
+ "rootDir": "src",
12
+ "declaration": false
13
+ },
14
+ "include": ["src/**/*"],
15
+ "exclude": ["node_modules", "dist"]
16
+ }