deepspace 0.4.4 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +90 -0
- package/dist/cli.js +2912 -1984
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +31 -4
- package/dist/index.js +511 -366
- package/dist/index.js.map +1 -1
- package/dist/server.d.ts +29 -108
- package/dist/server.js +126 -589
- package/dist/server.js.map +1 -1
- package/dist/testing.js.map +1 -1
- package/dist/worker.d.ts +29 -108
- package/dist/worker.js +126 -589
- package/dist/worker.js.map +1 -1
- package/features/admin-page/src/AdminPage.tsx +3 -5
- package/features/canvas/src/CanvasEditorPage.tsx +0 -2
- package/features/canvas/src/components/CanvasView.tsx +1 -1
- package/features/docs/src/LibrarySidebar.tsx +7 -1
- package/features/docs/src/use-yjs-room-with-awareness.ts +4 -0
- package/features/file-attachments/src/AttachmentsPage.tsx +7 -1
- package/features/file-attachments/src/useFilePreview.ts +7 -5
- package/features/file-manager/src/FileManagerPage.tsx +2 -3
- package/features/integration-test/feature.json +10 -3
- package/features/integration-test/src/IntegrationTestPage.tsx +15 -12
- package/features/landing/src/LandingPage.tsx +1 -1
- package/features/landing/src/sections/FeaturesGridSection.tsx +0 -92
- package/features/messaging/src/components/chat/MessageItem.tsx +5 -1
- package/features/messaging/src/hooks/useChatChannel.ts +5 -1
- package/features/presence-test/feature.json +2 -1
- package/features/sidebar/src/AppSidebar.tsx +1 -1
- package/features/tasks/src/TasksPage.tsx +1 -1
- package/features/testing/feature.json +2 -1
- package/features/testing/src/TestPage.tsx +8 -8
- package/package.json +4 -4
- package/scripts/add-feature.cjs +277 -272
package/scripts/add-feature.cjs
CHANGED
|
@@ -8,30 +8,32 @@
|
|
|
8
8
|
* Invoked by the `deepspace add` CLI in src/cli/commands/add.ts.
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
|
-
|
|
12
|
-
const
|
|
11
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports -- CommonJS (.cjs) module: require is required
|
|
12
|
+
const fs = require('fs')
|
|
13
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports -- CommonJS (.cjs) module: require is required
|
|
14
|
+
const path = require('path')
|
|
13
15
|
|
|
14
16
|
// ---------------------------------------------------------------------------
|
|
15
17
|
// Environment-aware features path resolution
|
|
16
18
|
// ---------------------------------------------------------------------------
|
|
17
19
|
|
|
18
20
|
function findFeaturesDir() {
|
|
19
|
-
const candidate = path.resolve(__dirname, '..', 'features')
|
|
20
|
-
if (fs.existsSync(candidate)) return candidate
|
|
21
|
-
console.error('Error: Could not find features directory at ' + candidate)
|
|
22
|
-
process.exit(1)
|
|
21
|
+
const candidate = path.resolve(__dirname, '..', 'features')
|
|
22
|
+
if (fs.existsSync(candidate)) return candidate
|
|
23
|
+
console.error('Error: Could not find features directory at ' + candidate)
|
|
24
|
+
process.exit(1)
|
|
23
25
|
}
|
|
24
26
|
|
|
25
|
-
const FEATURES_DIR = findFeaturesDir()
|
|
27
|
+
const FEATURES_DIR = findFeaturesDir()
|
|
26
28
|
|
|
27
29
|
// ---------------------------------------------------------------------------
|
|
28
30
|
// Resolve target directory
|
|
29
31
|
// ---------------------------------------------------------------------------
|
|
30
32
|
|
|
31
33
|
function resolveTargetDir(raw) {
|
|
32
|
-
if (!raw) return null
|
|
33
|
-
if (path.isAbsolute(raw)) return raw
|
|
34
|
-
return path.resolve(raw)
|
|
34
|
+
if (!raw) return null
|
|
35
|
+
if (path.isAbsolute(raw)) return raw
|
|
36
|
+
return path.resolve(raw)
|
|
35
37
|
}
|
|
36
38
|
|
|
37
39
|
// ---------------------------------------------------------------------------
|
|
@@ -45,51 +47,53 @@ const CATEGORY_LABELS = {
|
|
|
45
47
|
layout: 'Layouts',
|
|
46
48
|
display: 'Display',
|
|
47
49
|
landing: 'Landing Page Sections',
|
|
48
|
-
}
|
|
50
|
+
}
|
|
49
51
|
|
|
50
|
-
const CATEGORY_ORDER = ['assistant', 'data', 'nav', 'layout', 'display', 'landing']
|
|
52
|
+
const CATEGORY_ORDER = ['assistant', 'data', 'nav', 'layout', 'display', 'landing']
|
|
51
53
|
|
|
52
54
|
// ---------------------------------------------------------------------------
|
|
53
55
|
// Core helpers
|
|
54
56
|
// ---------------------------------------------------------------------------
|
|
55
57
|
|
|
56
58
|
function loadFeatureConfig(featureId) {
|
|
57
|
-
const configPath = path.join(FEATURES_DIR, featureId, 'feature.json')
|
|
58
|
-
if (!fs.existsSync(configPath)) return null
|
|
59
|
+
const configPath = path.join(FEATURES_DIR, featureId, 'feature.json')
|
|
60
|
+
if (!fs.existsSync(configPath)) return null
|
|
59
61
|
try {
|
|
60
|
-
return JSON.parse(fs.readFileSync(configPath, 'utf-8'))
|
|
62
|
+
return JSON.parse(fs.readFileSync(configPath, 'utf-8'))
|
|
61
63
|
} catch (e) {
|
|
62
|
-
console.error(`Error reading ${configPath}:`, e.message)
|
|
63
|
-
return null
|
|
64
|
+
console.error(`Error reading ${configPath}:`, e.message)
|
|
65
|
+
return null
|
|
64
66
|
}
|
|
65
67
|
}
|
|
66
68
|
|
|
67
69
|
function listFeatures() {
|
|
68
|
-
const entries = fs.readdirSync(FEATURES_DIR, { withFileTypes: true })
|
|
69
|
-
const configs = []
|
|
70
|
+
const entries = fs.readdirSync(FEATURES_DIR, { withFileTypes: true })
|
|
71
|
+
const configs = []
|
|
70
72
|
for (const e of entries) {
|
|
71
73
|
if (e.isDirectory() && e.name !== 'scripts') {
|
|
72
|
-
const config = loadFeatureConfig(e.name)
|
|
73
|
-
|
|
74
|
+
const config = loadFeatureConfig(e.name)
|
|
75
|
+
// Internal features (e2e/QA fixtures) are installable by explicit name
|
|
76
|
+
// but hidden from `--list` and the interactive picker.
|
|
77
|
+
if (config && !config.internal) configs.push(config)
|
|
74
78
|
}
|
|
75
79
|
}
|
|
76
|
-
return configs
|
|
80
|
+
return configs
|
|
77
81
|
}
|
|
78
82
|
|
|
79
83
|
function groupByCategory(features) {
|
|
80
|
-
const groups = new Map()
|
|
84
|
+
const groups = new Map()
|
|
81
85
|
for (const f of features) {
|
|
82
|
-
const category = f.category || 'other'
|
|
83
|
-
if (!groups.has(category)) groups.set(category, [])
|
|
84
|
-
groups.get(category).push(f)
|
|
86
|
+
const category = f.category || 'other'
|
|
87
|
+
if (!groups.has(category)) groups.set(category, [])
|
|
88
|
+
groups.get(category).push(f)
|
|
85
89
|
}
|
|
86
|
-
return groups
|
|
90
|
+
return groups
|
|
87
91
|
}
|
|
88
92
|
|
|
89
93
|
function copyFile(src, dest) {
|
|
90
|
-
const destDir = path.dirname(dest)
|
|
91
|
-
if (!fs.existsSync(destDir)) fs.mkdirSync(destDir, { recursive: true })
|
|
92
|
-
fs.copyFileSync(src, dest)
|
|
94
|
+
const destDir = path.dirname(dest)
|
|
95
|
+
if (!fs.existsSync(destDir)) fs.mkdirSync(destDir, { recursive: true })
|
|
96
|
+
fs.copyFileSync(src, dest)
|
|
93
97
|
}
|
|
94
98
|
|
|
95
99
|
// Copy + rewrite leading `../` imports. Used when a page file is redirected
|
|
@@ -99,11 +103,11 @@ function copyFile(src, dest) {
|
|
|
99
103
|
// (e.g. `import('../hooks/x').T` in a type position, or `await import('../x')`).
|
|
100
104
|
// Same-directory (`./...`) and bare-package imports are untouched.
|
|
101
105
|
function copyFileWithImportShift(src, dest) {
|
|
102
|
-
const destDir = path.dirname(dest)
|
|
103
|
-
if (!fs.existsSync(destDir)) fs.mkdirSync(destDir, { recursive: true })
|
|
104
|
-
const content = fs.readFileSync(src, 'utf-8')
|
|
105
|
-
const shifted = content.replace(/(from\s+|import\s+|import\()(['"])\.\.\//g, '$1$2../../')
|
|
106
|
-
fs.writeFileSync(dest, shifted)
|
|
106
|
+
const destDir = path.dirname(dest)
|
|
107
|
+
if (!fs.existsSync(destDir)) fs.mkdirSync(destDir, { recursive: true })
|
|
108
|
+
const content = fs.readFileSync(src, 'utf-8')
|
|
109
|
+
const shifted = content.replace(/(from\s+|import\s+|import\()(['"])\.\.\//g, '$1$2../../')
|
|
110
|
+
fs.writeFileSync(dest, shifted)
|
|
107
111
|
}
|
|
108
112
|
|
|
109
113
|
// ---------------------------------------------------------------------------
|
|
@@ -116,45 +120,45 @@ function copyFileWithImportShift(src, dest) {
|
|
|
116
120
|
// Only page files (src/pages/*) get rewritten; schemas, components, etc.
|
|
117
121
|
// keep their declared destinations.
|
|
118
122
|
function resolveDestPath(fileDest, route) {
|
|
119
|
-
const PAGES = 'src/pages/'
|
|
120
|
-
const PROTECTED = 'src/pages/(protected)/'
|
|
121
|
-
const isProtected = route && route.protected !== false
|
|
122
|
-
if (!isProtected) return fileDest
|
|
123
|
-
if (!fileDest.startsWith(PAGES) || fileDest.startsWith(PROTECTED)) return fileDest
|
|
124
|
-
return PROTECTED + fileDest.slice(PAGES.length)
|
|
123
|
+
const PAGES = 'src/pages/'
|
|
124
|
+
const PROTECTED = 'src/pages/(protected)/'
|
|
125
|
+
const isProtected = route && route.protected !== false
|
|
126
|
+
if (!isProtected) return fileDest
|
|
127
|
+
if (!fileDest.startsWith(PAGES) || fileDest.startsWith(PROTECTED)) return fileDest
|
|
128
|
+
return PROTECTED + fileDest.slice(PAGES.length)
|
|
125
129
|
}
|
|
126
130
|
|
|
127
131
|
function installFeature(config, targetDir) {
|
|
128
|
-
let copied = 0
|
|
129
|
-
let skipped = 0
|
|
132
|
+
let copied = 0
|
|
133
|
+
let skipped = 0
|
|
130
134
|
|
|
131
135
|
for (const file of config.files) {
|
|
132
|
-
const srcPath = path.join(FEATURES_DIR, config.id, file.src)
|
|
133
|
-
const resolvedDest = resolveDestPath(file.dest, config.route)
|
|
134
|
-
const destPath = path.join(targetDir, resolvedDest)
|
|
135
|
-
const wasRedirected = resolvedDest !== file.dest
|
|
136
|
+
const srcPath = path.join(FEATURES_DIR, config.id, file.src)
|
|
137
|
+
const resolvedDest = resolveDestPath(file.dest, config.route)
|
|
138
|
+
const destPath = path.join(targetDir, resolvedDest)
|
|
139
|
+
const wasRedirected = resolvedDest !== file.dest
|
|
136
140
|
|
|
137
141
|
if (!fs.existsSync(srcPath)) {
|
|
138
|
-
console.error(` Warning: source not found: ${file.src}`)
|
|
139
|
-
continue
|
|
142
|
+
console.error(` Warning: source not found: ${file.src}`)
|
|
143
|
+
continue
|
|
140
144
|
}
|
|
141
145
|
|
|
142
146
|
if (fs.existsSync(destPath) && !file.overwrite) {
|
|
143
|
-
console.log(` Exists (skipped): ${resolvedDest}`)
|
|
144
|
-
skipped
|
|
147
|
+
console.log(` Exists (skipped): ${resolvedDest}`)
|
|
148
|
+
skipped++
|
|
145
149
|
} else {
|
|
146
|
-
const replacing = fs.existsSync(destPath)
|
|
150
|
+
const replacing = fs.existsSync(destPath)
|
|
147
151
|
if (wasRedirected) {
|
|
148
|
-
copyFileWithImportShift(srcPath, destPath)
|
|
152
|
+
copyFileWithImportShift(srcPath, destPath)
|
|
149
153
|
} else {
|
|
150
|
-
copyFile(srcPath, destPath)
|
|
154
|
+
copyFile(srcPath, destPath)
|
|
151
155
|
}
|
|
152
|
-
console.log(` ${replacing ? 'Overwrote' : 'Copied'}: ${resolvedDest}`)
|
|
153
|
-
copied
|
|
156
|
+
console.log(` ${replacing ? 'Overwrote' : 'Copied'}: ${resolvedDest}`)
|
|
157
|
+
copied++
|
|
154
158
|
}
|
|
155
159
|
}
|
|
156
160
|
|
|
157
|
-
return { copied, skipped }
|
|
161
|
+
return { copied, skipped }
|
|
158
162
|
}
|
|
159
163
|
|
|
160
164
|
// ---------------------------------------------------------------------------
|
|
@@ -162,72 +166,72 @@ function installFeature(config, targetDir) {
|
|
|
162
166
|
// ---------------------------------------------------------------------------
|
|
163
167
|
|
|
164
168
|
function integrateSchema(config, targetDir) {
|
|
165
|
-
if (!config.schema) return false
|
|
169
|
+
if (!config.schema) return false
|
|
166
170
|
|
|
167
|
-
const schemasPath = path.join(targetDir, 'src', 'schemas.ts')
|
|
171
|
+
const schemasPath = path.join(targetDir, 'src', 'schemas.ts')
|
|
168
172
|
if (!fs.existsSync(schemasPath)) {
|
|
169
|
-
printSchemaInstructions(config.schema)
|
|
170
|
-
return false
|
|
173
|
+
printSchemaInstructions(config.schema)
|
|
174
|
+
return false
|
|
171
175
|
}
|
|
172
176
|
|
|
173
|
-
let content = fs.readFileSync(schemasPath, 'utf-8')
|
|
174
|
-
const { exportName, importPath, spreadOperator } = config.schema
|
|
177
|
+
let content = fs.readFileSync(schemasPath, 'utf-8')
|
|
178
|
+
const { exportName, importPath, spreadOperator } = config.schema
|
|
175
179
|
|
|
176
180
|
// Already integrated?
|
|
177
|
-
const importPattern = new RegExp(`^import\\s+\\{[^}]*\\b${exportName}\\b`, 'm')
|
|
181
|
+
const importPattern = new RegExp(`^import\\s+\\{[^}]*\\b${exportName}\\b`, 'm')
|
|
178
182
|
if (importPattern.test(content)) {
|
|
179
|
-
console.log(` Schema already present: ${exportName}`)
|
|
180
|
-
return false
|
|
183
|
+
console.log(` Schema already present: ${exportName}`)
|
|
184
|
+
return false
|
|
181
185
|
}
|
|
182
186
|
|
|
183
187
|
// Strict checks: verify the file has the expected structure
|
|
184
|
-
const hasExportConst = content.includes('export const schemas')
|
|
185
|
-
const hasArrayOpening = /export const schemas:\s*CollectionSchema\[\]\s*=\s*\[/.test(content)
|
|
188
|
+
const hasExportConst = content.includes('export const schemas')
|
|
189
|
+
const hasArrayOpening = /export const schemas:\s*CollectionSchema\[\]\s*=\s*\[/.test(content)
|
|
186
190
|
|
|
187
191
|
if (!hasExportConst || !hasArrayOpening) {
|
|
188
|
-
console.log(' Could not auto-integrate schema (schemas.ts has unexpected structure)')
|
|
189
|
-
printSchemaInstructions(config.schema)
|
|
190
|
-
return false
|
|
192
|
+
console.log(' Could not auto-integrate schema (schemas.ts has unexpected structure)')
|
|
193
|
+
printSchemaInstructions(config.schema)
|
|
194
|
+
return false
|
|
191
195
|
}
|
|
192
196
|
|
|
193
197
|
// 1. Add import before "export const schemas"
|
|
194
|
-
const importLine = `import { ${exportName} } from '${importPath}'
|
|
198
|
+
const importLine = `import { ${exportName} } from '${importPath}'`
|
|
195
199
|
const afterImport = content.replace(
|
|
196
200
|
/export const schemas/,
|
|
197
|
-
`${importLine}\n\nexport const schemas
|
|
198
|
-
)
|
|
201
|
+
`${importLine}\n\nexport const schemas`,
|
|
202
|
+
)
|
|
199
203
|
|
|
200
204
|
if (afterImport === content) {
|
|
201
|
-
console.log(' Could not insert import line')
|
|
202
|
-
printSchemaInstructions(config.schema)
|
|
203
|
-
return false
|
|
205
|
+
console.log(' Could not insert import line')
|
|
206
|
+
printSchemaInstructions(config.schema)
|
|
207
|
+
return false
|
|
204
208
|
}
|
|
205
209
|
|
|
206
210
|
// 2. Insert entry into the schemas array
|
|
207
|
-
const schemaEntry = spreadOperator ? `...${exportName}` : exportName
|
|
211
|
+
const schemaEntry = spreadOperator ? `...${exportName}` : exportName
|
|
208
212
|
const afterEntry = afterImport.replace(
|
|
209
213
|
/export const schemas:\s*CollectionSchema\[\]\s*=\s*\[/,
|
|
210
|
-
`export const schemas: CollectionSchema[] = [\n ${schemaEntry}
|
|
211
|
-
)
|
|
214
|
+
`export const schemas: CollectionSchema[] = [\n ${schemaEntry},`,
|
|
215
|
+
)
|
|
212
216
|
|
|
213
217
|
if (afterEntry === afterImport) {
|
|
214
|
-
console.log(' Could not insert schema entry')
|
|
215
|
-
printSchemaInstructions(config.schema)
|
|
216
|
-
return false
|
|
218
|
+
console.log(' Could not insert schema entry')
|
|
219
|
+
printSchemaInstructions(config.schema)
|
|
220
|
+
return false
|
|
217
221
|
}
|
|
218
222
|
|
|
219
|
-
fs.writeFileSync(schemasPath, afterEntry)
|
|
220
|
-
console.log(` Schema integrated: ${exportName} -> schemas.ts`)
|
|
221
|
-
return true
|
|
223
|
+
fs.writeFileSync(schemasPath, afterEntry)
|
|
224
|
+
console.log(` Schema integrated: ${exportName} -> schemas.ts`)
|
|
225
|
+
return true
|
|
222
226
|
}
|
|
223
227
|
|
|
224
228
|
function printSchemaInstructions(schema) {
|
|
225
|
-
const { exportName, importPath, spreadOperator } = schema
|
|
226
|
-
const entry = spreadOperator ? `...${exportName}` : exportName
|
|
227
|
-
console.log('')
|
|
228
|
-
console.log(' Add manually to src/schemas.ts:')
|
|
229
|
-
console.log(` import { ${exportName} } from '${importPath}'`)
|
|
230
|
-
console.log(` // then add ${entry} to the schemas array`)
|
|
229
|
+
const { exportName, importPath, spreadOperator } = schema
|
|
230
|
+
const entry = spreadOperator ? `...${exportName}` : exportName
|
|
231
|
+
console.log('')
|
|
232
|
+
console.log(' Add manually to src/schemas.ts:')
|
|
233
|
+
console.log(` import { ${exportName} } from '${importPath}'`)
|
|
234
|
+
console.log(` // then add ${entry} to the schemas array`)
|
|
231
235
|
}
|
|
232
236
|
|
|
233
237
|
// ---------------------------------------------------------------------------
|
|
@@ -235,76 +239,77 @@ function printSchemaInstructions(schema) {
|
|
|
235
239
|
// ---------------------------------------------------------------------------
|
|
236
240
|
|
|
237
241
|
function integrateActions(config, targetDir) {
|
|
238
|
-
if (!config.actions || config.actions.length === 0) return false
|
|
242
|
+
if (!config.actions || config.actions.length === 0) return false
|
|
239
243
|
|
|
240
|
-
const actionsPath = path.join(targetDir, 'src', 'actions', 'index.ts')
|
|
244
|
+
const actionsPath = path.join(targetDir, 'src', 'actions', 'index.ts')
|
|
241
245
|
if (!fs.existsSync(actionsPath)) {
|
|
242
|
-
printActionsInstructions(config.actions)
|
|
243
|
-
return false
|
|
246
|
+
printActionsInstructions(config.actions)
|
|
247
|
+
return false
|
|
244
248
|
}
|
|
245
249
|
|
|
246
|
-
let content = fs.readFileSync(actionsPath, 'utf-8')
|
|
250
|
+
let content = fs.readFileSync(actionsPath, 'utf-8')
|
|
247
251
|
|
|
248
252
|
// Strict check: must match the starter's shape so our regex patch is safe.
|
|
249
253
|
// Accepts both `ActionHandler` and `ActionHandler<Env>` (typed-env templates).
|
|
250
|
-
const hasRegistry =
|
|
254
|
+
const hasRegistry =
|
|
255
|
+
/export const actions:\s*Record<string,\s*ActionHandler(?:<[^>]*>)?>\s*=\s*\{/.test(content)
|
|
251
256
|
if (!hasRegistry) {
|
|
252
|
-
console.log(' Could not auto-integrate actions (actions/index.ts has unexpected structure)')
|
|
253
|
-
printActionsInstructions(config.actions)
|
|
254
|
-
return false
|
|
257
|
+
console.log(' Could not auto-integrate actions (actions/index.ts has unexpected structure)')
|
|
258
|
+
printActionsInstructions(config.actions)
|
|
259
|
+
return false
|
|
255
260
|
}
|
|
256
261
|
|
|
257
|
-
let integrated = 0
|
|
262
|
+
let integrated = 0
|
|
258
263
|
for (const action of config.actions) {
|
|
259
|
-
const { exportName, registerAs, importPath } = action
|
|
264
|
+
const { exportName, registerAs, importPath } = action
|
|
260
265
|
|
|
261
266
|
// Already integrated?
|
|
262
|
-
const importPattern = new RegExp(`^import\\s+\\{[^}]*\\b${exportName}\\b`, 'm')
|
|
267
|
+
const importPattern = new RegExp(`^import\\s+\\{[^}]*\\b${exportName}\\b`, 'm')
|
|
263
268
|
if (importPattern.test(content)) {
|
|
264
|
-
console.log(` Action already present: ${registerAs}`)
|
|
265
|
-
continue
|
|
269
|
+
console.log(` Action already present: ${registerAs}`)
|
|
270
|
+
continue
|
|
266
271
|
}
|
|
267
272
|
|
|
268
273
|
// 1. Add import before the registry export.
|
|
269
|
-
const importLine = `import { ${exportName} } from '${importPath}'
|
|
274
|
+
const importLine = `import { ${exportName} } from '${importPath}'`
|
|
270
275
|
const withImport = content.replace(
|
|
271
276
|
/export const actions:/,
|
|
272
277
|
`${importLine}\n\nexport const actions:`,
|
|
273
|
-
)
|
|
278
|
+
)
|
|
274
279
|
if (withImport === content) {
|
|
275
|
-
console.log(` Could not insert import for ${exportName}`)
|
|
276
|
-
continue
|
|
280
|
+
console.log(` Could not insert import for ${exportName}`)
|
|
281
|
+
continue
|
|
277
282
|
}
|
|
278
283
|
|
|
279
284
|
// 2. Insert entry into the registry object. Works for both empty and
|
|
280
285
|
// populated registries because we inject right after the opening brace.
|
|
281
|
-
const registryEntry = ` '${registerAs}': ${exportName}
|
|
286
|
+
const registryEntry = ` '${registerAs}': ${exportName},`
|
|
282
287
|
const withEntry = withImport.replace(
|
|
283
288
|
/export const actions:\s*Record<string,\s*ActionHandler(?:<[^>]*>)?>\s*=\s*\{/,
|
|
284
289
|
(match) => `${match}\n${registryEntry}`,
|
|
285
|
-
)
|
|
290
|
+
)
|
|
286
291
|
if (withEntry === withImport) {
|
|
287
|
-
console.log(` Could not insert registry entry for ${registerAs}`)
|
|
288
|
-
continue
|
|
292
|
+
console.log(` Could not insert registry entry for ${registerAs}`)
|
|
293
|
+
continue
|
|
289
294
|
}
|
|
290
295
|
|
|
291
|
-
content = withEntry
|
|
292
|
-
integrated
|
|
293
|
-
console.log(` Action integrated: ${registerAs} (${exportName})`)
|
|
296
|
+
content = withEntry
|
|
297
|
+
integrated++
|
|
298
|
+
console.log(` Action integrated: ${registerAs} (${exportName})`)
|
|
294
299
|
}
|
|
295
300
|
|
|
296
301
|
if (integrated > 0) {
|
|
297
|
-
fs.writeFileSync(actionsPath, content)
|
|
302
|
+
fs.writeFileSync(actionsPath, content)
|
|
298
303
|
}
|
|
299
|
-
return integrated > 0
|
|
304
|
+
return integrated > 0
|
|
300
305
|
}
|
|
301
306
|
|
|
302
307
|
function printActionsInstructions(actions) {
|
|
303
|
-
console.log('')
|
|
304
|
-
console.log(' Add manually to src/actions/index.ts:')
|
|
308
|
+
console.log('')
|
|
309
|
+
console.log(' Add manually to src/actions/index.ts:')
|
|
305
310
|
for (const a of actions) {
|
|
306
|
-
console.log(` import { ${a.exportName} } from '${a.importPath}'`)
|
|
307
|
-
console.log(` // then add '${a.registerAs}': ${a.exportName} to the actions object`)
|
|
311
|
+
console.log(` import { ${a.exportName} } from '${a.importPath}'`)
|
|
312
|
+
console.log(` // then add '${a.registerAs}': ${a.exportName} to the actions object`)
|
|
308
313
|
}
|
|
309
314
|
}
|
|
310
315
|
|
|
@@ -313,98 +318,98 @@ function printActionsInstructions(actions) {
|
|
|
313
318
|
// ---------------------------------------------------------------------------
|
|
314
319
|
|
|
315
320
|
function integrateCss(config, targetDir) {
|
|
316
|
-
if (!config.css || config.css.length === 0) return false
|
|
321
|
+
if (!config.css || config.css.length === 0) return false
|
|
317
322
|
|
|
318
|
-
const stylesPath = path.join(targetDir, 'src', 'styles.css')
|
|
323
|
+
const stylesPath = path.join(targetDir, 'src', 'styles.css')
|
|
319
324
|
if (!fs.existsSync(stylesPath)) {
|
|
320
|
-
console.log(' Warning: Cannot integrate CSS — src/styles.css not found')
|
|
321
|
-
return false
|
|
325
|
+
console.log(' Warning: Cannot integrate CSS — src/styles.css not found')
|
|
326
|
+
return false
|
|
322
327
|
}
|
|
323
328
|
|
|
324
|
-
let stylesContent = fs.readFileSync(stylesPath, 'utf-8')
|
|
325
|
-
let integrated = 0
|
|
329
|
+
let stylesContent = fs.readFileSync(stylesPath, 'utf-8')
|
|
330
|
+
let integrated = 0
|
|
326
331
|
|
|
327
332
|
for (const cssFile of config.css) {
|
|
328
|
-
const srcPath = path.join(FEATURES_DIR, config.id, cssFile)
|
|
333
|
+
const srcPath = path.join(FEATURES_DIR, config.id, cssFile)
|
|
329
334
|
if (!fs.existsSync(srcPath)) {
|
|
330
|
-
console.error(` Warning: CSS source not found: ${cssFile}`)
|
|
331
|
-
continue
|
|
335
|
+
console.error(` Warning: CSS source not found: ${cssFile}`)
|
|
336
|
+
continue
|
|
332
337
|
}
|
|
333
338
|
|
|
334
|
-
const cssContent = fs.readFileSync(srcPath, 'utf-8')
|
|
339
|
+
const cssContent = fs.readFileSync(srcPath, 'utf-8')
|
|
335
340
|
|
|
336
341
|
// Check if already integrated by looking for a unique class/keyframe from the CSS
|
|
337
342
|
// Use the first non-comment, non-empty line with a class or keyframe as fingerprint
|
|
338
|
-
const fingerprint = cssContent.match(/\.([\w-]+)\s*\{|@keyframes\s+([\w-]+)/)
|
|
343
|
+
const fingerprint = cssContent.match(/\.([\w-]+)\s*\{|@keyframes\s+([\w-]+)/)
|
|
339
344
|
if (fingerprint) {
|
|
340
|
-
const marker = fingerprint[1] || fingerprint[2]
|
|
345
|
+
const marker = fingerprint[1] || fingerprint[2]
|
|
341
346
|
if (stylesContent.includes(marker)) {
|
|
342
|
-
console.log(` CSS already present: ${cssFile} (found .${marker})`)
|
|
343
|
-
continue
|
|
347
|
+
console.log(` CSS already present: ${cssFile} (found .${marker})`)
|
|
348
|
+
continue
|
|
344
349
|
}
|
|
345
350
|
}
|
|
346
351
|
|
|
347
|
-
stylesContent += '\n' + cssContent
|
|
348
|
-
integrated
|
|
349
|
-
console.log(` CSS integrated: ${cssFile} -> styles.css`)
|
|
352
|
+
stylesContent += '\n' + cssContent
|
|
353
|
+
integrated++
|
|
354
|
+
console.log(` CSS integrated: ${cssFile} -> styles.css`)
|
|
350
355
|
}
|
|
351
356
|
|
|
352
357
|
if (integrated > 0) {
|
|
353
|
-
fs.writeFileSync(stylesPath, stylesContent)
|
|
358
|
+
fs.writeFileSync(stylesPath, stylesContent)
|
|
354
359
|
}
|
|
355
360
|
|
|
356
|
-
return integrated > 0
|
|
361
|
+
return integrated > 0
|
|
357
362
|
}
|
|
358
363
|
|
|
359
364
|
// ---------------------------------------------------------------------------
|
|
360
365
|
// Nav auto-wiring into nav.ts
|
|
361
366
|
// ---------------------------------------------------------------------------
|
|
362
367
|
|
|
363
|
-
const NAV_MARKER = '// ── Features add nav items below this line ──'
|
|
368
|
+
const NAV_MARKER = '// ── Features add nav items below this line ──'
|
|
364
369
|
|
|
365
370
|
function integrateRoute(config, targetDir) {
|
|
366
|
-
if (!config.route) return false
|
|
371
|
+
if (!config.route) return false
|
|
367
372
|
|
|
368
|
-
const navPath = path.join(targetDir, 'src', 'nav.ts')
|
|
373
|
+
const navPath = path.join(targetDir, 'src', 'nav.ts')
|
|
369
374
|
if (!fs.existsSync(navPath)) {
|
|
370
|
-
printRouteInstructions(config.route, config)
|
|
371
|
-
return false
|
|
375
|
+
printRouteInstructions(config.route, config)
|
|
376
|
+
return false
|
|
372
377
|
}
|
|
373
378
|
|
|
374
|
-
let content = fs.readFileSync(navPath, 'utf-8')
|
|
375
|
-
const { path: routePath } = config.route
|
|
376
|
-
const label = config.name
|
|
379
|
+
let content = fs.readFileSync(navPath, 'utf-8')
|
|
380
|
+
const { path: routePath } = config.route
|
|
381
|
+
const label = config.name
|
|
377
382
|
|
|
378
383
|
// Already wired?
|
|
379
384
|
if (content.includes(`'${routePath}'`)) {
|
|
380
|
-
console.log(` Nav already present: ${routePath}`)
|
|
381
|
-
return false
|
|
385
|
+
console.log(` Nav already present: ${routePath}`)
|
|
386
|
+
return false
|
|
382
387
|
}
|
|
383
388
|
|
|
384
389
|
if (!content.includes(NAV_MARKER)) {
|
|
385
|
-
console.log(' Could not auto-wire nav (marker not found in nav.ts)')
|
|
386
|
-
printRouteInstructions(config.route, config)
|
|
387
|
-
return false
|
|
390
|
+
console.log(' Could not auto-wire nav (marker not found in nav.ts)')
|
|
391
|
+
printRouteInstructions(config.route, config)
|
|
392
|
+
return false
|
|
388
393
|
}
|
|
389
394
|
|
|
390
|
-
const rolesStr = config.route.protected === false ? '' : ", roles: ['member' as Role]"
|
|
391
|
-
const entry = ` { path: '${routePath}', label: '${label}'${rolesStr} }
|
|
395
|
+
const rolesStr = config.route.protected === false ? '' : ", roles: ['member' as Role]"
|
|
396
|
+
const entry = ` { path: '${routePath}', label: '${label}'${rolesStr} },`
|
|
392
397
|
|
|
393
|
-
content = content.replace(NAV_MARKER, `${NAV_MARKER}\n${entry}`)
|
|
394
|
-
fs.writeFileSync(navPath, content)
|
|
395
|
-
console.log(` Nav wired: ${routePath} (${label})`)
|
|
398
|
+
content = content.replace(NAV_MARKER, `${NAV_MARKER}\n${entry}`)
|
|
399
|
+
fs.writeFileSync(navPath, content)
|
|
400
|
+
console.log(` Nav wired: ${routePath} (${label})`)
|
|
396
401
|
|
|
397
402
|
// Route is automatic via generouted — page file in src/pages/ is enough
|
|
398
|
-
console.log(` Route: automatic (file-based routing via src/pages/)`)
|
|
399
|
-
return true
|
|
403
|
+
console.log(` Route: automatic (file-based routing via src/pages/)`)
|
|
404
|
+
return true
|
|
400
405
|
}
|
|
401
406
|
|
|
402
407
|
function printRouteInstructions(route, config) {
|
|
403
|
-
const { path: routePath } = route
|
|
404
|
-
const label = config?.name ?? routePath
|
|
405
|
-
console.log('')
|
|
406
|
-
console.log(' Add manually to src/nav.ts:')
|
|
407
|
-
console.log(` { path: '${routePath}', label: '${label}' },`)
|
|
408
|
+
const { path: routePath } = route
|
|
409
|
+
const label = config?.name ?? routePath
|
|
410
|
+
console.log('')
|
|
411
|
+
console.log(' Add manually to src/nav.ts:')
|
|
412
|
+
console.log(` { path: '${routePath}', label: '${label}' },`)
|
|
408
413
|
}
|
|
409
414
|
|
|
410
415
|
// ---------------------------------------------------------------------------
|
|
@@ -427,58 +432,56 @@ function integrateDependencies(config, targetDir) {
|
|
|
427
432
|
const sections = [
|
|
428
433
|
{ key: 'dependencies', label: 'dependencies' },
|
|
429
434
|
{ key: 'devDependencies', label: 'devDependencies' },
|
|
430
|
-
]
|
|
435
|
+
]
|
|
431
436
|
|
|
432
437
|
// Skip the work if nothing is declared.
|
|
433
|
-
const hasAny = sections.some(
|
|
434
|
-
|
|
435
|
-
);
|
|
436
|
-
if (!hasAny) return;
|
|
438
|
+
const hasAny = sections.some((s) => config[s.key] && Object.keys(config[s.key]).length > 0)
|
|
439
|
+
if (!hasAny) return
|
|
437
440
|
|
|
438
|
-
const pkgPath = path.join(targetDir, 'package.json')
|
|
441
|
+
const pkgPath = path.join(targetDir, 'package.json')
|
|
439
442
|
if (!fs.existsSync(pkgPath)) {
|
|
440
|
-
console.log(' Could not patch package.json (file not found)')
|
|
441
|
-
return
|
|
443
|
+
console.log(' Could not patch package.json (file not found)')
|
|
444
|
+
return
|
|
442
445
|
}
|
|
443
446
|
|
|
444
|
-
let pkg
|
|
447
|
+
let pkg
|
|
445
448
|
try {
|
|
446
|
-
pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'))
|
|
449
|
+
pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'))
|
|
447
450
|
} catch (err) {
|
|
448
|
-
console.log(` Could not patch package.json (parse error: ${err.message})`)
|
|
449
|
-
return
|
|
451
|
+
console.log(` Could not patch package.json (parse error: ${err.message})`)
|
|
452
|
+
return
|
|
450
453
|
}
|
|
451
454
|
|
|
452
|
-
const added = []
|
|
453
|
-
const skipped = []
|
|
455
|
+
const added = []
|
|
456
|
+
const skipped = []
|
|
454
457
|
|
|
455
458
|
for (const { key } of sections) {
|
|
456
|
-
const declared = config[key]
|
|
457
|
-
if (!declared) continue
|
|
458
|
-
pkg[key] = pkg[key] || {}
|
|
459
|
+
const declared = config[key]
|
|
460
|
+
if (!declared) continue
|
|
461
|
+
pkg[key] = pkg[key] || {}
|
|
459
462
|
for (const [name, version] of Object.entries(declared)) {
|
|
460
463
|
if (pkg[key][name]) {
|
|
461
|
-
skipped.push(name)
|
|
462
|
-
continue
|
|
464
|
+
skipped.push(name)
|
|
465
|
+
continue
|
|
463
466
|
}
|
|
464
|
-
pkg[key][name] = version
|
|
465
|
-
added.push(name)
|
|
467
|
+
pkg[key][name] = version
|
|
468
|
+
added.push(name)
|
|
466
469
|
}
|
|
467
470
|
}
|
|
468
471
|
|
|
469
472
|
if (added.length === 0) {
|
|
470
|
-
console.log(' Dependencies already present')
|
|
471
|
-
return
|
|
473
|
+
console.log(' Dependencies already present')
|
|
474
|
+
return
|
|
472
475
|
}
|
|
473
476
|
|
|
474
477
|
// Re-write with two-space indent (Node's de-facto default; matches
|
|
475
478
|
// what most generators / lint tools agree on).
|
|
476
|
-
fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + '\n', 'utf-8')
|
|
477
|
-
console.log(` Added to package.json: ${added.join(', ')}`)
|
|
479
|
+
fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + '\n', 'utf-8')
|
|
480
|
+
console.log(` Added to package.json: ${added.join(', ')}`)
|
|
478
481
|
if (skipped.length > 0) {
|
|
479
|
-
console.log(` Already present (kept your version): ${skipped.join(', ')}`)
|
|
482
|
+
console.log(` Already present (kept your version): ${skipped.join(', ')}`)
|
|
480
483
|
}
|
|
481
|
-
console.log(' ➜ Run `npm install` (or your package manager) before starting the dev server')
|
|
484
|
+
console.log(' ➜ Run `npm install` (or your package manager) before starting the dev server')
|
|
482
485
|
}
|
|
483
486
|
|
|
484
487
|
// ---------------------------------------------------------------------------
|
|
@@ -486,13 +489,13 @@ function integrateDependencies(config, targetDir) {
|
|
|
486
489
|
// ---------------------------------------------------------------------------
|
|
487
490
|
|
|
488
491
|
function printPostInstallInstructions(config) {
|
|
489
|
-
const instructions = config.instructions || []
|
|
492
|
+
const instructions = config.instructions || []
|
|
490
493
|
|
|
491
494
|
if (instructions.length > 0) {
|
|
492
|
-
console.log('\n--- Manual wiring needed ---\n')
|
|
495
|
+
console.log('\n--- Manual wiring needed ---\n')
|
|
493
496
|
instructions.forEach((inst, i) => {
|
|
494
|
-
console.log(`${i + 1}. ${inst}\n`)
|
|
495
|
-
})
|
|
497
|
+
console.log(`${i + 1}. ${inst}\n`)
|
|
498
|
+
})
|
|
496
499
|
}
|
|
497
500
|
}
|
|
498
501
|
|
|
@@ -501,137 +504,139 @@ function printPostInstallInstructions(config) {
|
|
|
501
504
|
// ---------------------------------------------------------------------------
|
|
502
505
|
|
|
503
506
|
function main() {
|
|
504
|
-
const args = process.argv.slice(2)
|
|
507
|
+
const args = process.argv.slice(2)
|
|
505
508
|
|
|
506
509
|
// --help
|
|
507
510
|
if (args.length === 0 || args[0] === '--help' || args[0] === '-h') {
|
|
508
|
-
console.log('\nFeature Installation Script\n')
|
|
509
|
-
console.log('Usage: node add-feature.js <feature-id> <app-dir>\n')
|
|
510
|
-
console.log('Commands:')
|
|
511
|
-
console.log(' <feature-id> <dir> Install a feature into app dir')
|
|
512
|
-
console.log(' --list, -l List all available features')
|
|
513
|
-
console.log(' --info <id> Show detailed info about a feature')
|
|
514
|
-
console.log(' --help, -h Show this help\n')
|
|
511
|
+
console.log('\nFeature Installation Script\n')
|
|
512
|
+
console.log('Usage: node add-feature.js <feature-id> <app-dir>\n')
|
|
513
|
+
console.log('Commands:')
|
|
514
|
+
console.log(' <feature-id> <dir> Install a feature into app dir')
|
|
515
|
+
console.log(' --list, -l List all available features')
|
|
516
|
+
console.log(' --info <id> Show detailed info about a feature')
|
|
517
|
+
console.log(' --help, -h Show this help\n')
|
|
515
518
|
|
|
516
|
-
const features = listFeatures()
|
|
517
|
-
const groups = groupByCategory(features)
|
|
519
|
+
const features = listFeatures()
|
|
520
|
+
const groups = groupByCategory(features)
|
|
518
521
|
|
|
519
522
|
for (const category of CATEGORY_ORDER) {
|
|
520
|
-
const categoryFeatures = groups.get(category)
|
|
523
|
+
const categoryFeatures = groups.get(category)
|
|
521
524
|
if (categoryFeatures && categoryFeatures.length > 0) {
|
|
522
|
-
console.log(`\n${CATEGORY_LABELS[category] || category}:`)
|
|
525
|
+
console.log(`\n${CATEGORY_LABELS[category] || category}:`)
|
|
523
526
|
for (const f of categoryFeatures) {
|
|
524
|
-
console.log(` ${f.id.padEnd(22)} ${f.name}`)
|
|
527
|
+
console.log(` ${f.id.padEnd(22)} ${f.name}`)
|
|
525
528
|
}
|
|
526
529
|
}
|
|
527
530
|
}
|
|
528
531
|
|
|
529
|
-
console.log('\nExamples:')
|
|
530
|
-
console.log(' add feature items-crud .')
|
|
531
|
-
console.log(' node add-feature.js items-crud ../my-app')
|
|
532
|
-
process.exit(0)
|
|
532
|
+
console.log('\nExamples:')
|
|
533
|
+
console.log(' add feature items-crud .')
|
|
534
|
+
console.log(' node add-feature.js items-crud ../my-app')
|
|
535
|
+
process.exit(0)
|
|
533
536
|
}
|
|
534
537
|
|
|
535
538
|
// --list
|
|
536
539
|
if (args[0] === '--list' || args[0] === '-l') {
|
|
537
|
-
console.log('\nAvailable Features\n')
|
|
540
|
+
console.log('\nAvailable Features\n')
|
|
538
541
|
|
|
539
|
-
const features = listFeatures()
|
|
540
|
-
const groups = groupByCategory(features)
|
|
542
|
+
const features = listFeatures()
|
|
543
|
+
const groups = groupByCategory(features)
|
|
541
544
|
|
|
542
545
|
for (const category of CATEGORY_ORDER) {
|
|
543
|
-
const categoryFeatures = groups.get(category)
|
|
546
|
+
const categoryFeatures = groups.get(category)
|
|
544
547
|
if (categoryFeatures && categoryFeatures.length > 0) {
|
|
545
|
-
console.log(`${CATEGORY_LABELS[category] || category}:`)
|
|
548
|
+
console.log(`${CATEGORY_LABELS[category] || category}:`)
|
|
546
549
|
for (const f of categoryFeatures) {
|
|
547
|
-
console.log(` ${f.id.padEnd(22)} ${f.name.padEnd(24)} ${f.description}`)
|
|
550
|
+
console.log(` ${f.id.padEnd(22)} ${f.name.padEnd(24)} ${f.description}`)
|
|
548
551
|
}
|
|
549
|
-
console.log('')
|
|
552
|
+
console.log('')
|
|
550
553
|
}
|
|
551
554
|
}
|
|
552
555
|
|
|
553
|
-
console.log('Use: npx deepspace add <feature-id>')
|
|
554
|
-
process.exit(0)
|
|
556
|
+
console.log('Use: npx deepspace add <feature-id>')
|
|
557
|
+
process.exit(0)
|
|
555
558
|
}
|
|
556
559
|
|
|
557
560
|
// --info
|
|
558
561
|
if (args[0] === '--info' || args[0] === '-i') {
|
|
559
|
-
const featureId = args[1]
|
|
562
|
+
const featureId = args[1]
|
|
560
563
|
if (!featureId) {
|
|
561
|
-
console.error('\nError: Please specify a feature ID')
|
|
562
|
-
process.exit(1)
|
|
564
|
+
console.error('\nError: Please specify a feature ID')
|
|
565
|
+
process.exit(1)
|
|
563
566
|
}
|
|
564
|
-
const config = loadFeatureConfig(featureId)
|
|
567
|
+
const config = loadFeatureConfig(featureId)
|
|
565
568
|
if (!config) {
|
|
566
|
-
console.error(`\nError: Unknown feature: ${featureId}`)
|
|
567
|
-
console.error('Use --list to see available features')
|
|
568
|
-
process.exit(1)
|
|
569
|
+
console.error(`\nError: Unknown feature: ${featureId}`)
|
|
570
|
+
console.error('Use --list to see available features')
|
|
571
|
+
process.exit(1)
|
|
569
572
|
}
|
|
570
573
|
|
|
571
|
-
console.log(`\n${config.name} (${config.id})`)
|
|
574
|
+
console.log(`\n${config.name} (${config.id})`)
|
|
572
575
|
if (config.category) {
|
|
573
|
-
console.log(` Category: ${CATEGORY_LABELS[config.category] || config.category}`)
|
|
576
|
+
console.log(` Category: ${CATEGORY_LABELS[config.category] || config.category}`)
|
|
574
577
|
}
|
|
575
|
-
console.log(` ${config.description}\n`)
|
|
576
|
-
console.log(` ${config.details}\n`)
|
|
578
|
+
console.log(` ${config.description}\n`)
|
|
579
|
+
console.log(` ${config.details}\n`)
|
|
577
580
|
|
|
578
|
-
console.log(' Files:')
|
|
579
|
-
config.files.forEach(f =>
|
|
581
|
+
console.log(' Files:')
|
|
582
|
+
config.files.forEach((f) =>
|
|
583
|
+
console.log(` - ${f.src} -> ${resolveDestPath(f.dest, config.route)}`),
|
|
584
|
+
)
|
|
580
585
|
|
|
581
586
|
if (config.instructions && config.instructions.length > 0) {
|
|
582
|
-
console.log('\n Integration steps:')
|
|
583
|
-
config.instructions.forEach((inst, i) => console.log(` ${i + 1}. ${inst}`))
|
|
587
|
+
console.log('\n Integration steps:')
|
|
588
|
+
config.instructions.forEach((inst, i) => console.log(` ${i + 1}. ${inst}`))
|
|
584
589
|
}
|
|
585
590
|
|
|
586
591
|
if (config.patterns && config.patterns.length > 0) {
|
|
587
|
-
console.log('\n Patterns:')
|
|
588
|
-
config.patterns.forEach(p => console.log(` - ${p}`))
|
|
592
|
+
console.log('\n Patterns:')
|
|
593
|
+
config.patterns.forEach((p) => console.log(` - ${p}`))
|
|
589
594
|
}
|
|
590
595
|
|
|
591
596
|
if (config.example) {
|
|
592
|
-
console.log('\n Example:')
|
|
593
|
-
config.example.split('\n').forEach(line => console.log(` ${line}`))
|
|
597
|
+
console.log('\n Example:')
|
|
598
|
+
config.example.split('\n').forEach((line) => console.log(` ${line}`))
|
|
594
599
|
}
|
|
595
|
-
console.log('')
|
|
596
|
-
process.exit(0)
|
|
600
|
+
console.log('')
|
|
601
|
+
process.exit(0)
|
|
597
602
|
}
|
|
598
603
|
|
|
599
604
|
// Install feature
|
|
600
|
-
const featureId = args[0]
|
|
601
|
-
const rawDir = args[1]
|
|
602
|
-
const targetDir = resolveTargetDir(rawDir)
|
|
605
|
+
const featureId = args[0]
|
|
606
|
+
const rawDir = args[1]
|
|
607
|
+
const targetDir = resolveTargetDir(rawDir)
|
|
603
608
|
|
|
604
609
|
if (!targetDir) {
|
|
605
|
-
console.error('\nError: Please specify an app directory')
|
|
606
|
-
console.error('Usage: node add-feature.js <feature-id> <app-dir>')
|
|
607
|
-
process.exit(1)
|
|
610
|
+
console.error('\nError: Please specify an app directory')
|
|
611
|
+
console.error('Usage: node add-feature.js <feature-id> <app-dir>')
|
|
612
|
+
process.exit(1)
|
|
608
613
|
}
|
|
609
614
|
|
|
610
|
-
const config = loadFeatureConfig(featureId)
|
|
615
|
+
const config = loadFeatureConfig(featureId)
|
|
611
616
|
if (!config) {
|
|
612
|
-
console.error(`\nError: Unknown feature: ${featureId}`)
|
|
613
|
-
console.error('Use --list to see available features')
|
|
614
|
-
process.exit(1)
|
|
617
|
+
console.error(`\nError: Unknown feature: ${featureId}`)
|
|
618
|
+
console.error('Use --list to see available features')
|
|
619
|
+
process.exit(1)
|
|
615
620
|
}
|
|
616
621
|
|
|
617
622
|
if (!fs.existsSync(targetDir)) {
|
|
618
|
-
console.error(`\nError: Target directory not found: ${targetDir}`)
|
|
619
|
-
process.exit(1)
|
|
623
|
+
console.error(`\nError: Target directory not found: ${targetDir}`)
|
|
624
|
+
process.exit(1)
|
|
620
625
|
}
|
|
621
626
|
|
|
622
|
-
console.log(`\nInstalling: ${config.name}`)
|
|
623
|
-
console.log(` ${config.description}`)
|
|
624
|
-
console.log(` Target: ${targetDir}`)
|
|
627
|
+
console.log(`\nInstalling: ${config.name}`)
|
|
628
|
+
console.log(` ${config.description}`)
|
|
629
|
+
console.log(` Target: ${targetDir}`)
|
|
625
630
|
|
|
626
|
-
const { copied, skipped } = installFeature(config, targetDir)
|
|
631
|
+
const { copied, skipped } = installFeature(config, targetDir)
|
|
627
632
|
|
|
628
|
-
console.log(`\nCopied ${copied} file(s)${skipped > 0 ? `, skipped ${skipped} existing` : ''}`)
|
|
633
|
+
console.log(`\nCopied ${copied} file(s)${skipped > 0 ? `, skipped ${skipped} existing` : ''}`)
|
|
629
634
|
|
|
630
635
|
// Auto-integrate schema, actions, CSS, and route
|
|
631
|
-
integrateSchema(config, targetDir)
|
|
632
|
-
integrateActions(config, targetDir)
|
|
633
|
-
integrateCss(config, targetDir)
|
|
634
|
-
integrateRoute(config, targetDir)
|
|
636
|
+
integrateSchema(config, targetDir)
|
|
637
|
+
integrateActions(config, targetDir)
|
|
638
|
+
integrateCss(config, targetDir)
|
|
639
|
+
integrateRoute(config, targetDir)
|
|
635
640
|
|
|
636
641
|
// Add any new npm dependencies the feature declares to the target's
|
|
637
642
|
// package.json. Without this, copied source files import packages
|
|
@@ -640,16 +645,16 @@ function main() {
|
|
|
640
645
|
// package.json (rather than running `npm install` ourselves) so the
|
|
641
646
|
// user keeps control over their package manager + lockfile, and we
|
|
642
647
|
// print a single clear instruction when changes were made.
|
|
643
|
-
integrateDependencies(config, targetDir)
|
|
648
|
+
integrateDependencies(config, targetDir)
|
|
644
649
|
|
|
645
650
|
// Print any remaining manual wiring instructions
|
|
646
|
-
printPostInstallInstructions(config)
|
|
651
|
+
printPostInstallInstructions(config)
|
|
647
652
|
|
|
648
653
|
if (config.patterns && config.patterns.length > 0) {
|
|
649
|
-
console.log('--- Key patterns ---\n')
|
|
650
|
-
config.patterns.forEach(p => console.log(`- ${p}`))
|
|
651
|
-
console.log('')
|
|
654
|
+
console.log('--- Key patterns ---\n')
|
|
655
|
+
config.patterns.forEach((p) => console.log(`- ${p}`))
|
|
656
|
+
console.log('')
|
|
652
657
|
}
|
|
653
658
|
}
|
|
654
659
|
|
|
655
|
-
main()
|
|
660
|
+
main()
|