@storystick/cli 0.0.0-stage → 0.2.0-rc.1
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/CONTENT-IMPORT.md +349 -0
- package/LICENSE +112 -0
- package/PUBLIC-API.md +30 -0
- package/README.md +263 -2
- package/assets/next-v1/app/[[...pathname]]/error.tsx +10 -0
- package/assets/next-v1/app/[[...pathname]]/not-found.tsx +8 -0
- package/assets/next-v1/app/[[...pathname]]/page.tsx +38 -0
- package/assets/next-v1/app/api/storystick/[...action]/route.ts +4 -0
- package/assets/next-v1/app/layout.tsx +28 -0
- package/assets/next-v1/app/sitemap.ts +3 -0
- package/assets/next-v1/env.local.example +13 -0
- package/assets/next-v1/globals.css +17 -0
- package/assets/next-v1/lib/site.ts +10 -0
- package/assets/next-v1/next.config.ts +4 -0
- package/assets/skills/storystick-development/SKILL.md +79 -0
- package/assets/skills/storystick-development/references/setup.md +152 -0
- package/assets/skills/storystick-development/references/story-and-next.md +49 -0
- package/dist/_backfill-recommendation.d.ts +12 -0
- package/dist/_backfill-recommendation.js +18 -0
- package/dist/asset-import.d.ts +50 -0
- package/dist/asset-import.js +160 -0
- package/dist/assistant-read-client.d.ts +3 -0
- package/dist/assistant-read-client.js +8 -0
- package/dist/auth-device.d.ts +153 -0
- package/dist/auth-device.js +390 -0
- package/dist/bootstrap-client.d.ts +52 -0
- package/dist/bootstrap-client.js +19 -0
- package/dist/cli-error.d.ts +54 -0
- package/dist/cli-error.js +107 -0
- package/dist/cli.d.ts +68 -0
- package/dist/cli.js +1520 -0
- package/dist/commands/archive.d.ts +17 -0
- package/dist/commands/archive.js +86 -0
- package/dist/commands/backfill-refs.d.ts +54 -0
- package/dist/commands/backfill-refs.js +217 -0
- package/dist/commands/connect.d.ts +11 -0
- package/dist/commands/connect.js +124 -0
- package/dist/commands/connection.d.ts +57 -0
- package/dist/commands/connection.js +83 -0
- package/dist/commands/content.d.ts +11 -0
- package/dist/commands/content.js +107 -0
- package/dist/commands/delivery.d.ts +22 -0
- package/dist/commands/delivery.js +99 -0
- package/dist/commands/info.d.ts +35 -0
- package/dist/commands/info.js +62 -0
- package/dist/commands/list.d.ts +28 -0
- package/dist/commands/list.js +116 -0
- package/dist/commands/migrate.d.ts +80 -0
- package/dist/commands/migrate.js +550 -0
- package/dist/commands/release.d.ts +8 -0
- package/dist/commands/release.js +60 -0
- package/dist/commands/setup.d.ts +6 -0
- package/dist/commands/setup.js +58 -0
- package/dist/commands/slice-previews.d.ts +17 -0
- package/dist/commands/slice-previews.js +233 -0
- package/dist/commands/source.d.ts +82 -0
- package/dist/commands/source.js +192 -0
- package/dist/content-import.d.ts +78 -0
- package/dist/content-import.js +210 -0
- package/dist/generated-transaction.d.ts +22 -0
- package/dist/generated-transaction.js +330 -0
- package/dist/hub-client.d.ts +72 -0
- package/dist/hub-client.js +178 -0
- package/dist/init-operation.d.ts +98 -0
- package/dist/init-operation.js +427 -0
- package/dist/init-readiness.d.ts +10 -0
- package/dist/init-readiness.js +50 -0
- package/dist/init-scaffold.d.ts +54 -0
- package/dist/init-scaffold.js +439 -0
- package/dist/init.d.ts +51 -0
- package/dist/init.js +789 -0
- package/dist/mcp-execution.d.ts +15 -0
- package/dist/mcp-execution.js +90 -0
- package/dist/mcp.d.ts +7 -0
- package/dist/mcp.js +41 -0
- package/dist/migration-artifact.d.ts +13 -0
- package/dist/migration-artifact.js +154 -0
- package/dist/operations-client.d.ts +137 -0
- package/dist/operations-client.js +130 -0
- package/dist/output.d.ts +347 -0
- package/dist/output.js +771 -0
- package/dist/project-access.d.ts +18 -0
- package/dist/project-access.js +1 -0
- package/dist/project-connection.d.ts +62 -0
- package/dist/project-connection.js +347 -0
- package/dist/project-execution-client.d.ts +2 -0
- package/dist/project-execution-client.js +33 -0
- package/dist/push-client.d.ts +100 -0
- package/dist/push-client.js +160 -0
- package/dist/release-context.d.ts +13 -0
- package/dist/release-context.js +39 -0
- package/dist/release-schema-client.d.ts +16 -0
- package/dist/release-schema-client.js +33 -0
- package/dist/release-sync.d.ts +36 -0
- package/dist/release-sync.js +31 -0
- package/dist/resolve.d.ts +15 -0
- package/dist/resolve.js +80 -0
- package/dist/runtime-config.d.ts +18 -0
- package/dist/runtime-config.js +86 -0
- package/dist/schema-export.d.ts +10 -0
- package/dist/schema-export.js +28 -0
- package/dist/setup.d.ts +65 -0
- package/dist/setup.js +135 -0
- package/dist/source-git.d.ts +36 -0
- package/dist/source-git.js +197 -0
- package/dist/starter-resolver.d.ts +82 -0
- package/dist/starter-resolver.js +495 -0
- package/dist/sync-transaction.d.ts +29 -0
- package/dist/sync-transaction.js +159 -0
- package/dist/transport.d.ts +8 -0
- package/dist/transport.js +73 -0
- package/examples/sanity/README.md +79 -0
- package/examples/sanity/convert.mjs +228 -0
- package/examples/sanity/fixture/data.ndjson +7 -0
- package/examples/sanity/fixture/mapping.json +44 -0
- package/examples/sanity/fixture/originals/mark.svg +1 -0
- package/examples/sanity/stories/articleBody.story +23 -0
- package/examples/sanity/stories/copy.story +7 -0
- package/examples/sanity/stories/page.story +19 -0
- package/examples/sanity/stories/picture.story +7 -0
- package/examples/sanity/stories/settings.story +9 -0
- package/examples/sanity/stories/stack.story +10 -0
- package/package.json +75 -3
|
@@ -0,0 +1,439 @@
|
|
|
1
|
+
import { existsSync, lstatSync, mkdirSync, readFileSync, readdirSync, rmSync, writeFileSync, } from 'node:fs';
|
|
2
|
+
import { fileURLToPath } from 'node:url';
|
|
3
|
+
import { dirname, join, relative, resolve, sep } from 'node:path';
|
|
4
|
+
import { StorystickError } from './cli-error.js';
|
|
5
|
+
const KERNEL_ROOT = fileURLToPath(new URL('../assets/next-v1/', import.meta.url));
|
|
6
|
+
const GITIGNORE_ENTRIES = [
|
|
7
|
+
'node_modules/',
|
|
8
|
+
'.next/',
|
|
9
|
+
'.env.local',
|
|
10
|
+
'generated/',
|
|
11
|
+
'.storystick/',
|
|
12
|
+
];
|
|
13
|
+
export function isNextProject(rootDir) {
|
|
14
|
+
const packageJson = readPackageJson(rootDir);
|
|
15
|
+
if (packageJson === null)
|
|
16
|
+
return false;
|
|
17
|
+
return Boolean(packageJson.dependencies?.next ??
|
|
18
|
+
packageJson.devDependencies?.next ??
|
|
19
|
+
packageJson.peerDependencies?.next);
|
|
20
|
+
}
|
|
21
|
+
export function planInitScaffold({ rootDir, starterRoot, manifest, force = false, }) {
|
|
22
|
+
if (manifest.framework !== 'next')
|
|
23
|
+
throw new StorystickError('The Next integration kernel cannot be applied to an Astro starter.', { code: 'INIT_FRAMEWORK_MISMATCH', category: 'validation' });
|
|
24
|
+
const appDir = detectAppDir(rootDir);
|
|
25
|
+
const componentsDir = appDir.startsWith('src/')
|
|
26
|
+
? 'src/components'
|
|
27
|
+
: 'components';
|
|
28
|
+
const libDir = appDir.startsWith('src/') ? 'src/lib' : 'lib';
|
|
29
|
+
const catchAllDir = join(appDir, '[[...pathname]]');
|
|
30
|
+
const actions = [];
|
|
31
|
+
actions.push(...planStarterOverlay({
|
|
32
|
+
rootDir,
|
|
33
|
+
starterRoot,
|
|
34
|
+
manifest,
|
|
35
|
+
componentsDir,
|
|
36
|
+
force,
|
|
37
|
+
}));
|
|
38
|
+
// The customer's native root layout stays at its native location.
|
|
39
|
+
const rootLayoutPath = join(appDir, 'layout.tsx');
|
|
40
|
+
const replaceRoot = existsSync(join(rootDir, rootLayoutPath)) && isReplaceableRootLayout(readFileSync(join(rootDir, rootLayoutPath), 'utf8'));
|
|
41
|
+
actions.push(planKernelFile({ rootDir, path: rootLayoutPath, asset: 'app/layout.tsx', manifest, force: force || replaceRoot, critical: true }));
|
|
42
|
+
const rootPagePath = join(appDir, 'page.tsx');
|
|
43
|
+
const rootPageAction = planRootPageConflictAction(rootDir, rootPagePath, force);
|
|
44
|
+
const canCreateCatchAll = !existsSync(join(rootDir, rootPagePath)) ||
|
|
45
|
+
rootPageAction?.status === 'delete';
|
|
46
|
+
if (rootPageAction)
|
|
47
|
+
actions.push(rootPageAction);
|
|
48
|
+
actions.push(canCreateCatchAll
|
|
49
|
+
? planKernelFile({
|
|
50
|
+
rootDir,
|
|
51
|
+
path: join(catchAllDir, 'page.tsx'),
|
|
52
|
+
asset: 'app/[[...pathname]]/page.tsx',
|
|
53
|
+
manifest,
|
|
54
|
+
force,
|
|
55
|
+
critical: true,
|
|
56
|
+
})
|
|
57
|
+
: {
|
|
58
|
+
kind: 'file',
|
|
59
|
+
path: join(catchAllDir, 'page.tsx'),
|
|
60
|
+
status: 'skip',
|
|
61
|
+
content: renderKernelAsset('app/[[...pathname]]/page.tsx', manifest),
|
|
62
|
+
reason: `${rootPagePath} conflicts with the optional catch-all; ` +
|
|
63
|
+
'move it or re-run with --force',
|
|
64
|
+
critical: true,
|
|
65
|
+
}, planKernelFile({
|
|
66
|
+
rootDir,
|
|
67
|
+
path: join(catchAllDir, 'not-found.tsx'),
|
|
68
|
+
asset: 'app/[[...pathname]]/not-found.tsx',
|
|
69
|
+
manifest,
|
|
70
|
+
force,
|
|
71
|
+
critical: true,
|
|
72
|
+
}), planKernelFile({
|
|
73
|
+
rootDir,
|
|
74
|
+
path: join(catchAllDir, 'error.tsx'),
|
|
75
|
+
asset: 'app/[[...pathname]]/error.tsx',
|
|
76
|
+
manifest,
|
|
77
|
+
force,
|
|
78
|
+
critical: true,
|
|
79
|
+
}), planKernelFile({
|
|
80
|
+
rootDir,
|
|
81
|
+
path: join(appDir, 'api', 'storystick', '[...action]', 'route.ts'),
|
|
82
|
+
asset: 'app/api/storystick/[...action]/route.ts',
|
|
83
|
+
manifest,
|
|
84
|
+
force,
|
|
85
|
+
critical: true,
|
|
86
|
+
}), planKernelFile({
|
|
87
|
+
rootDir,
|
|
88
|
+
path: join(appDir, 'sitemap.ts'),
|
|
89
|
+
asset: 'app/sitemap.ts',
|
|
90
|
+
manifest,
|
|
91
|
+
force,
|
|
92
|
+
critical: true,
|
|
93
|
+
}), planKernelFile({
|
|
94
|
+
rootDir,
|
|
95
|
+
path: join(libDir, 'site.ts'),
|
|
96
|
+
asset: 'lib/site.ts',
|
|
97
|
+
manifest,
|
|
98
|
+
force,
|
|
99
|
+
critical: true,
|
|
100
|
+
}), planNextConfigAction(rootDir, manifest, force), planOptionalGlobalsAction(rootDir, join(appDir, 'globals.css'), manifest), planKernelFile({
|
|
101
|
+
rootDir,
|
|
102
|
+
path: '.env.local.example',
|
|
103
|
+
asset: 'env.local.example',
|
|
104
|
+
manifest,
|
|
105
|
+
force,
|
|
106
|
+
}), planPackageAction(rootDir, starterRoot), planGitignoreAction(rootDir));
|
|
107
|
+
return { rootDir, appDir, componentsDir, actions };
|
|
108
|
+
}
|
|
109
|
+
export function assertInitScaffoldReady(plan) {
|
|
110
|
+
const blocked = plan.actions.filter((action) => action.status === 'skip' &&
|
|
111
|
+
'critical' in action &&
|
|
112
|
+
action.critical === true &&
|
|
113
|
+
action.reason !== 'already up to date');
|
|
114
|
+
if (blocked.length === 0)
|
|
115
|
+
return;
|
|
116
|
+
throw new StorystickError(`Storystick cannot safely integrate ${blocked
|
|
117
|
+
.map((action) => `${action.path}: ${action.reason ?? 'conflict'}`)
|
|
118
|
+
.join('; ')}`, {
|
|
119
|
+
code: 'INIT_SCAFFOLD_CONFLICT',
|
|
120
|
+
category: 'conflict',
|
|
121
|
+
recoverable: true,
|
|
122
|
+
payload: { paths: blocked.map((action) => action.path) },
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
export function writeInitScaffold(plan) {
|
|
126
|
+
assertInitScaffoldReady(plan);
|
|
127
|
+
for (const action of plan.actions) {
|
|
128
|
+
if (action.status === 'skip')
|
|
129
|
+
continue;
|
|
130
|
+
const absolute = join(plan.rootDir, action.path);
|
|
131
|
+
if (action.kind === 'delete') {
|
|
132
|
+
rmSync(absolute, { force: true });
|
|
133
|
+
continue;
|
|
134
|
+
}
|
|
135
|
+
mkdirSync(dirname(absolute), { recursive: true });
|
|
136
|
+
writeFileSync(absolute, action.content, 'utf8');
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
export function summarizeInitScaffold(plan) {
|
|
140
|
+
return {
|
|
141
|
+
changed: plan.actions.filter((action) => action.status !== 'skip').length,
|
|
142
|
+
skipped: plan.actions.filter((action) => action.status === 'skip'),
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
export function renderKernelAsset(asset, manifest) {
|
|
146
|
+
const fields = manifest.integration.fields;
|
|
147
|
+
const replacements = {
|
|
148
|
+
'{{ROUTE_STORY_TYPES}}': JSON.stringify(manifest.integration.routeStoryTypes),
|
|
149
|
+
'{{TITLE_FIELD}}': JSON.stringify(fields.title),
|
|
150
|
+
'{{DESCRIPTION_FIELD}}': JSON.stringify(fields.description),
|
|
151
|
+
'{{SLICES_FIELD}}': JSON.stringify(fields.slices),
|
|
152
|
+
'{{SITE_SETTINGS_STORY}}': JSON.stringify(fields.siteSettings),
|
|
153
|
+
'{{SITE_NAME_FIELD}}': fields.siteName,
|
|
154
|
+
'{{SITE_DESCRIPTION_FIELD}}': fields.siteDescription,
|
|
155
|
+
};
|
|
156
|
+
let source = readFileSync(join(KERNEL_ROOT, asset), 'utf8');
|
|
157
|
+
for (const [token, value] of Object.entries(replacements)) {
|
|
158
|
+
source = source.replaceAll(token, value);
|
|
159
|
+
}
|
|
160
|
+
if (/\{\{[A-Z_]+\}\}/u.test(source)) {
|
|
161
|
+
throw new StorystickError(`Unresolved integration token in ${asset}.`, {
|
|
162
|
+
code: 'INIT_KERNEL_INVALID',
|
|
163
|
+
category: 'system',
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
return source;
|
|
167
|
+
}
|
|
168
|
+
function planStarterOverlay({ rootDir, starterRoot, manifest, componentsDir, force, }) {
|
|
169
|
+
const actions = [];
|
|
170
|
+
const destinations = new Map();
|
|
171
|
+
for (const mapping of manifest.integration.existingNext ?? []) {
|
|
172
|
+
const source = join(starterRoot, mapping.from);
|
|
173
|
+
if (!existsSync(source) || !lstatSync(source).isDirectory()) {
|
|
174
|
+
throw new StorystickError(`Starter overlay source must be a directory: ${mapping.from}`, { code: 'STARTER_OVERLAY_MISSING', category: 'validation' });
|
|
175
|
+
}
|
|
176
|
+
const destination = mapping.to === 'stories'
|
|
177
|
+
? 'stories'
|
|
178
|
+
: join(componentsDir, relative('components', mapping.to));
|
|
179
|
+
for (const file of listFiles(source)) {
|
|
180
|
+
const path = join(destination, file);
|
|
181
|
+
const previous = destinations.get(path);
|
|
182
|
+
if (previous) {
|
|
183
|
+
throw new StorystickError(`Starter overlay mappings "${previous}" and "${mapping.from}" both publish ${path}.`, { code: 'STARTER_OVERLAY_COLLISION', category: 'validation' });
|
|
184
|
+
}
|
|
185
|
+
destinations.set(path, mapping.from);
|
|
186
|
+
actions.push(planFileAction({
|
|
187
|
+
rootDir,
|
|
188
|
+
path,
|
|
189
|
+
content: readFileSync(join(source, file), 'utf8'),
|
|
190
|
+
force,
|
|
191
|
+
critical: true,
|
|
192
|
+
}));
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
return actions;
|
|
196
|
+
}
|
|
197
|
+
function planKernelFile({ rootDir, path, asset, manifest, force, critical = false, }) {
|
|
198
|
+
return planFileAction({
|
|
199
|
+
rootDir,
|
|
200
|
+
path,
|
|
201
|
+
content: renderKernelAsset(asset, manifest),
|
|
202
|
+
force,
|
|
203
|
+
critical,
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
function planFileAction({ rootDir, path, content, force, critical = false, }) {
|
|
207
|
+
const absolute = join(rootDir, path);
|
|
208
|
+
if (!existsSync(absolute)) {
|
|
209
|
+
return { kind: 'file', path, status: 'create', content, critical };
|
|
210
|
+
}
|
|
211
|
+
const current = readFileSync(absolute, 'utf8');
|
|
212
|
+
if (current === content) {
|
|
213
|
+
return {
|
|
214
|
+
kind: 'file',
|
|
215
|
+
path,
|
|
216
|
+
status: 'skip',
|
|
217
|
+
content,
|
|
218
|
+
reason: 'already up to date',
|
|
219
|
+
critical,
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
if (force) {
|
|
223
|
+
return { kind: 'file', path, status: 'overwrite', content, critical };
|
|
224
|
+
}
|
|
225
|
+
return {
|
|
226
|
+
kind: 'file',
|
|
227
|
+
path,
|
|
228
|
+
status: 'skip',
|
|
229
|
+
content,
|
|
230
|
+
reason: 'exists; re-run with --force to overwrite',
|
|
231
|
+
critical,
|
|
232
|
+
};
|
|
233
|
+
}
|
|
234
|
+
function planNextConfigAction(rootDir, manifest, _force) {
|
|
235
|
+
const existing = findExistingNextConfig(rootDir);
|
|
236
|
+
if (existing)
|
|
237
|
+
return { kind: 'file', path: existing, status: 'skip', content: readFileSync(join(rootDir, existing), 'utf8'), reason: 'native Next configuration preserved', critical: false };
|
|
238
|
+
return { kind: 'file', path: 'next.config.ts', status: 'create', content: renderKernelAsset('next.config.ts', manifest), critical: false };
|
|
239
|
+
}
|
|
240
|
+
function planOptionalGlobalsAction(rootDir, path, manifest) {
|
|
241
|
+
const content = renderKernelAsset('globals.css', manifest);
|
|
242
|
+
const absolute = join(rootDir, path);
|
|
243
|
+
return existsSync(absolute)
|
|
244
|
+
? {
|
|
245
|
+
kind: 'file',
|
|
246
|
+
path,
|
|
247
|
+
status: 'skip',
|
|
248
|
+
content: readFileSync(absolute, 'utf8'),
|
|
249
|
+
reason: 'customer styles preserved',
|
|
250
|
+
}
|
|
251
|
+
: { kind: 'file', path, status: 'create', content };
|
|
252
|
+
}
|
|
253
|
+
function planRootPageConflictAction(rootDir, path, force) {
|
|
254
|
+
const absolute = join(rootDir, path);
|
|
255
|
+
if (!existsSync(absolute))
|
|
256
|
+
return null;
|
|
257
|
+
const current = readFileSync(absolute, 'utf8');
|
|
258
|
+
if (force || isCreateNextAppPlaceholderPage(current)) {
|
|
259
|
+
return {
|
|
260
|
+
kind: 'delete',
|
|
261
|
+
path,
|
|
262
|
+
status: 'delete',
|
|
263
|
+
reason: 'removed because it conflicts with the Storystick optional catch-all',
|
|
264
|
+
};
|
|
265
|
+
}
|
|
266
|
+
return null;
|
|
267
|
+
}
|
|
268
|
+
function planPackageAction(rootDir, starterRoot) {
|
|
269
|
+
const path = join(rootDir, 'package.json');
|
|
270
|
+
const current = readPackageJson(rootDir) ?? {};
|
|
271
|
+
const next = mergePackageJson(current, starterRoot);
|
|
272
|
+
const content = `${JSON.stringify(next, null, 2)}\n`;
|
|
273
|
+
if (!existsSync(path)) {
|
|
274
|
+
return { kind: 'package', path: 'package.json', status: 'create', content };
|
|
275
|
+
}
|
|
276
|
+
if (readFileSync(path, 'utf8') === content) {
|
|
277
|
+
return {
|
|
278
|
+
kind: 'package',
|
|
279
|
+
path: 'package.json',
|
|
280
|
+
status: 'skip',
|
|
281
|
+
content,
|
|
282
|
+
reason: 'already up to date',
|
|
283
|
+
};
|
|
284
|
+
}
|
|
285
|
+
return { kind: 'package', path: 'package.json', status: 'update', content };
|
|
286
|
+
}
|
|
287
|
+
function mergePackageJson(packageJson, starterRoot) {
|
|
288
|
+
// Both init strategies use the selected, verified artifact's package contract.
|
|
289
|
+
const starterPackage = readPackageJson(starterRoot);
|
|
290
|
+
const nextVersion = starterPackage?.dependencies?.['@storystick/next'];
|
|
291
|
+
const cliVersion = starterPackage?.devDependencies?.['@storystick/cli'];
|
|
292
|
+
if (typeof nextVersion !== 'string' || !nextVersion.trim() ||
|
|
293
|
+
typeof cliVersion !== 'string' || !cliVersion.trim()) {
|
|
294
|
+
throw new StorystickError('Starter must declare @storystick/next and @storystick/cli package versions.', {
|
|
295
|
+
code: 'STARTER_MANIFEST_INVALID', category: 'validation',
|
|
296
|
+
});
|
|
297
|
+
}
|
|
298
|
+
const scripts = asRecord(packageJson.scripts);
|
|
299
|
+
const dependencies = asRecord(packageJson.dependencies);
|
|
300
|
+
const devDependencies = asRecord(packageJson.devDependencies);
|
|
301
|
+
const imports = asRecord(packageJson.imports);
|
|
302
|
+
const buildScript = typeof scripts.build === 'string' && scripts.build.length > 0
|
|
303
|
+
? scripts.build
|
|
304
|
+
: 'next build';
|
|
305
|
+
const generate = 'storystick generate --root . --stories stories';
|
|
306
|
+
const buildHasGenerate = buildScript.includes('storystick generate') ||
|
|
307
|
+
buildScript.includes('storystick:generate');
|
|
308
|
+
return {
|
|
309
|
+
...packageJson,
|
|
310
|
+
imports: {
|
|
311
|
+
...imports,
|
|
312
|
+
'#storystick/*': './generated/storystick/*.ts',
|
|
313
|
+
},
|
|
314
|
+
scripts: {
|
|
315
|
+
...scripts,
|
|
316
|
+
'storystick:generate': generate,
|
|
317
|
+
'storystick:push': 'storystick push --root . --stories stories',
|
|
318
|
+
'storystick:sync': 'storystick sync --root . --stories stories',
|
|
319
|
+
build: buildHasGenerate ? buildScript : `${generate} && ${buildScript}`,
|
|
320
|
+
},
|
|
321
|
+
dependencies: {
|
|
322
|
+
...dependencies,
|
|
323
|
+
'@storystick/next': dependencies['@storystick/next'] ?? nextVersion,
|
|
324
|
+
},
|
|
325
|
+
devDependencies: {
|
|
326
|
+
...devDependencies,
|
|
327
|
+
'@storystick/cli': devDependencies['@storystick/cli'] ?? cliVersion,
|
|
328
|
+
},
|
|
329
|
+
};
|
|
330
|
+
}
|
|
331
|
+
function planGitignoreAction(rootDir) {
|
|
332
|
+
const path = '.gitignore';
|
|
333
|
+
const absolute = join(rootDir, path);
|
|
334
|
+
if (!existsSync(absolute)) {
|
|
335
|
+
return {
|
|
336
|
+
kind: 'gitignore',
|
|
337
|
+
path,
|
|
338
|
+
status: 'create',
|
|
339
|
+
content: `${GITIGNORE_ENTRIES.join('\n')}\n`,
|
|
340
|
+
};
|
|
341
|
+
}
|
|
342
|
+
const current = readFileSync(absolute, 'utf8');
|
|
343
|
+
const lines = new Set(current
|
|
344
|
+
.split(/\r?\n/u)
|
|
345
|
+
.map((line) => line.trim())
|
|
346
|
+
.filter(Boolean));
|
|
347
|
+
const missing = GITIGNORE_ENTRIES.filter((entry) => !lines.has(entry));
|
|
348
|
+
if (missing.length === 0) {
|
|
349
|
+
return {
|
|
350
|
+
kind: 'gitignore',
|
|
351
|
+
path,
|
|
352
|
+
status: 'skip',
|
|
353
|
+
content: current,
|
|
354
|
+
reason: 'already up to date',
|
|
355
|
+
};
|
|
356
|
+
}
|
|
357
|
+
const separator = current.length === 0 || current.endsWith('\n') ? '' : '\n';
|
|
358
|
+
return {
|
|
359
|
+
kind: 'gitignore',
|
|
360
|
+
path,
|
|
361
|
+
status: 'update',
|
|
362
|
+
content: `${current}${separator}${missing.join('\n')}\n`,
|
|
363
|
+
};
|
|
364
|
+
}
|
|
365
|
+
function detectAppDir(rootDir) {
|
|
366
|
+
if (existsSync(join(rootDir, 'src', 'app')))
|
|
367
|
+
return 'src/app';
|
|
368
|
+
if (existsSync(join(rootDir, 'app')))
|
|
369
|
+
return 'app';
|
|
370
|
+
if (existsSync(join(rootDir, 'src')))
|
|
371
|
+
return 'src/app';
|
|
372
|
+
return 'app';
|
|
373
|
+
}
|
|
374
|
+
function findExistingNextConfig(rootDir) {
|
|
375
|
+
for (const candidate of [
|
|
376
|
+
'next.config.ts',
|
|
377
|
+
'next.config.mjs',
|
|
378
|
+
'next.config.js',
|
|
379
|
+
'next.config.cjs',
|
|
380
|
+
]) {
|
|
381
|
+
if (existsSync(join(rootDir, candidate)))
|
|
382
|
+
return candidate;
|
|
383
|
+
}
|
|
384
|
+
return null;
|
|
385
|
+
}
|
|
386
|
+
function isDefaultNextConfig(source) {
|
|
387
|
+
return (source.includes('const nextConfig') &&
|
|
388
|
+
source.includes('export default nextConfig') &&
|
|
389
|
+
(/const nextConfig(?::\s*NextConfig)?\s*=\s*\{\s*\}/mu.test(source) ||
|
|
390
|
+
source.includes('/* config options here */')));
|
|
391
|
+
}
|
|
392
|
+
function isCreateNextAppPlaceholderPage(source) {
|
|
393
|
+
return (source.includes('export default function Home') &&
|
|
394
|
+
source.includes('Get started by editing') &&
|
|
395
|
+
source.includes('next/image'));
|
|
396
|
+
}
|
|
397
|
+
function isReplaceableRootLayout(source) {
|
|
398
|
+
return ((source.includes('@storystick/next') && source.includes('<Storystick')) ||
|
|
399
|
+
source.includes('Geist_Mono') ||
|
|
400
|
+
source.includes('create-next-app') ||
|
|
401
|
+
(source.includes('Create Next App') &&
|
|
402
|
+
source.includes('Generated by create next app')));
|
|
403
|
+
}
|
|
404
|
+
function readPackageJson(rootDir) {
|
|
405
|
+
const path = join(rootDir, 'package.json');
|
|
406
|
+
if (!existsSync(path))
|
|
407
|
+
return null;
|
|
408
|
+
try {
|
|
409
|
+
return JSON.parse(readFileSync(path, 'utf8'));
|
|
410
|
+
}
|
|
411
|
+
catch {
|
|
412
|
+
return null;
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
function asRecord(value) {
|
|
416
|
+
if (value === null || typeof value !== 'object' || Array.isArray(value)) {
|
|
417
|
+
return {};
|
|
418
|
+
}
|
|
419
|
+
return { ...value };
|
|
420
|
+
}
|
|
421
|
+
function listFiles(root) {
|
|
422
|
+
const files = [];
|
|
423
|
+
const visit = (directory) => {
|
|
424
|
+
for (const entry of readdirSync(directory).sort()) {
|
|
425
|
+
const absolute = join(directory, entry);
|
|
426
|
+
const status = lstatSync(absolute);
|
|
427
|
+
if (status.isSymbolicLink()) {
|
|
428
|
+
throw new StorystickError(`Starter overlay contains an unsupported link: ${relative(root, absolute)}`, { code: 'STARTER_ARTIFACT_UNSAFE', category: 'validation' });
|
|
429
|
+
}
|
|
430
|
+
if (status.isDirectory())
|
|
431
|
+
visit(absolute);
|
|
432
|
+
else if (status.isFile()) {
|
|
433
|
+
files.push(relative(root, absolute).split(sep).join('/'));
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
};
|
|
437
|
+
visit(resolve(root));
|
|
438
|
+
return files;
|
|
439
|
+
}
|
package/dist/init.d.ts
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { type InitOperationReceipt } from './init-operation.js';
|
|
2
|
+
export interface InitOptions {
|
|
3
|
+
link?: string;
|
|
4
|
+
team?: string;
|
|
5
|
+
newTeam?: string;
|
|
6
|
+
into?: string;
|
|
7
|
+
origin?: string;
|
|
8
|
+
locale?: string;
|
|
9
|
+
siteUrl?: string;
|
|
10
|
+
starter?: string;
|
|
11
|
+
force?: boolean;
|
|
12
|
+
json?: boolean;
|
|
13
|
+
/** @internal deterministic idempotency seam. */
|
|
14
|
+
operationId?: string;
|
|
15
|
+
}
|
|
16
|
+
export interface EligibleProjectTeam {
|
|
17
|
+
shortId: string;
|
|
18
|
+
name: string;
|
|
19
|
+
role: 'owner' | 'admin';
|
|
20
|
+
isPersonal: boolean;
|
|
21
|
+
}
|
|
22
|
+
interface ProjectTeamSelectionOptions {
|
|
23
|
+
requestedShortId?: string;
|
|
24
|
+
newTeamName?: string;
|
|
25
|
+
interactive: boolean;
|
|
26
|
+
prompt?: (message: string, choices: Array<{
|
|
27
|
+
value: string;
|
|
28
|
+
name: string;
|
|
29
|
+
description: string;
|
|
30
|
+
}>) => Promise<string>;
|
|
31
|
+
}
|
|
32
|
+
export declare function runInit(projectNameArg?: string, options?: InitOptions): Promise<void>;
|
|
33
|
+
export declare function sanitizedInstallEnvironment(env?: NodeJS.ProcessEnv): NodeJS.ProcessEnv;
|
|
34
|
+
export declare function installDependencies(targetDir: string, signal?: AbortSignal): Promise<void>;
|
|
35
|
+
export declare function relativeTarget(targetDir: string, cwd?: string): string;
|
|
36
|
+
export declare function selectEligibleProjectTeam(teams: EligibleProjectTeam[], options: ProjectTeamSelectionOptions): Promise<EligibleProjectTeam | {
|
|
37
|
+
newTeamName: string;
|
|
38
|
+
}>;
|
|
39
|
+
export declare function fetchEligibleProjectTeams(signal?: AbortSignal, rootDir?: string): Promise<EligibleProjectTeam[]>;
|
|
40
|
+
export declare function printInitOutro(args: {
|
|
41
|
+
dirName: string;
|
|
42
|
+
shortId: string;
|
|
43
|
+
projectName: string;
|
|
44
|
+
team?: {
|
|
45
|
+
shortId: string;
|
|
46
|
+
name: string;
|
|
47
|
+
};
|
|
48
|
+
linked?: boolean;
|
|
49
|
+
receipt?: InitOperationReceipt;
|
|
50
|
+
}): void;
|
|
51
|
+
export {};
|